Note:
If you don’t like reading, just scroll down to the end of this page to view the full source code.
…
…copying…
…
Note:
You might wonder why this is done at all. We could similarly retrieve this person from the person-collection and applying a filter, etc.
Yes, it is true, often there are several ways of achieving the desired goal.
However, small differences can be in the way how a service is used and in performance considerations. Imagine a smartphone app which has strong requirements to limit the network traffic, so going through the flow, navigate from one screen to the next and only then a small amount of data is fetched by the underlying OData service.
@Read(serviceName = "DemoService", entity = "People", sourceEntity = "Jobs")
public ReadResponse getPersonForJob(ReadRequest request) {
Integer jobId = (Integer) request.getSourceKeys().get("JobId");
// identify those people who have this job
List<Map<String,Object>> allPeopleList = getAllPeople();
List<Map<String,Object>> peopleForJobList = new ArrayList<Map<String, Object>>();
for(Map<String, Object> personMap : allPeopleList) {
if(((Integer)personMap.get("JobId")).equals(jobId)) {
peopleForJobList.add(personMap);
}
}
Integer requestedPersonId = (Integer) request.getKeys().get("PersonId");
Note that here we use request.getKeys() whereas in step 1 we called request.getSourceKeys()
Note that we don’t search in the list of all existing people. We have to search in the temporary list which we’ve filled in step 1.
for(Map<String, Object> person : peopleForJobList){
if (((Integer)person.get("PersonId")).equals(requestedPersonId)) {
// found the person
return ReadResponse.setSuccess().setData(person).response();
}
}
return ReadResponse.setError(
ErrorResponse.getBuilder()
.setMessage("Error: Couldn't find requested person")
.setStatusCode(404)
.response());
“If the target URL for the collection is a navigation link, the new entity is automatically linked to the entity containing the navigation link.”
<Property Name="PersonId" Type="Edm.Int32" />
<Property Name="Name" Type="Edm.String" />
<Property Name="JobId" Type="Edm.Int32" />
{
"PersonId": 6,
"Name": "Martin",
"JobId": 1
}
@Create(serviceName = "DemoService", entity = "People", sourceEntity = "Jobs")
public CreateResponse createPersonForJob(CreateRequest request) {
Integer jobIdFromRequest = (Integer) request.getSourceKeys().get("JobId");
Map<String, Object> requestBody = request.getMapData();
Integer idToCreate = (Integer) requestBody.get("PersonId");
String nameToCreate = (String) requestBody.get("Name");
Map<String, Object> createdPerson = createPerson(idToCreate, nameToCreate, jobIdFromRequest);
Note:
It is important that we store the created instance of “Person” in a variable like "createdPerson"
Why?
Because we have to set is in the response body.
Why this cannot be done by the FWK?
Because in this special case, the real response is different from the data which is sent in the request body, so the FWK cannot know.
E.g., as I mentioned, if the user specifies a “JobId” in the request payload, we will ignore it and create the person with the correct “JobId”, which is in the URL
Therefore, we have to return exactly the data which we have created
return CreateResponse.setSuccess().setData(createdPerson).response();
URL | https://<host>/odata/v4/DemoService/Jobs(3)/Employees |
HTTP Verb | POST |
Request body | { "PersonId": 6, "Name": "Maxi" } |
Request Header | Name: Content-Type Value: application/json |
Note:
I know that you’re curious…
You’re wondering: what is the FWK doing? How can it do the expand? Is there some black magic happening…?
Don’t worry, the FWK is just a human, like us.
What it does, when an expand is detected, the FWK calls our implementation for the “normal” QUERY (or: READ) and for each (or: the) entry, it follows the navigation property and calls our implementation for it.
I know that you’re skeptical…
You’re wondering: urgh – is that efficient…?
Don’t worry, the FWK is just a human, like us.
In this case it is us who need to decide if we can afford to rely on the FWK-functionality. Of course, such generic support cannot be performant. But for small amount of data it is just nice to get it for free.
Stay tuned to learn how to overwrite such generic FWK-functionality with own specific implementation.
<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" />
<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>
<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>
package com.example.DemoService;
import java.util.ArrayList;
import java.util.HashMap;
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.Query;
import com.sap.cloud.sdk.service.prov.api.operations.Read;
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.response.CreateResponse;
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;
public class ServiceImplementation {
private static final List<Map<String, Object>> peopleList = new ArrayList<Map<String, Object>>();
private static final List<Map<String, Object>> jobList = new ArrayList<Map<String, Object>>();
/* PERSON */
@Query(serviceName="DemoService", entity="People")
public QueryResponse getPeople(QueryRequest request) {
List<Map<String, Object>> peopleList = getAllPeople();
return QueryResponse.setSuccess().setDataAsMap(peopleList).response();
}
@Read(serviceName="DemoService", entity="People")
public ReadResponse getPerson(ReadRequest request) {
Integer id = (Integer) request.getKeys().get("PersonId");
Map<String, Object> personMap = findPerson(id);
return ReadResponse.setSuccess().setData(personMap).response();
}
/* JOB */
@Query(serviceName="DemoService", entity="Jobs")
public QueryResponse getJobs(QueryRequest request) {
List<Map<String, Object>> jobList = getAllJobs();
return QueryResponse.setSuccess().setDataAsMap(jobList).response();
}
@Read(serviceName="DemoService", entity="Jobs")
public ReadResponse getJob(ReadRequest request) {
Integer id = (Integer) request.getKeys().get("JobId");
Map<String, Object> jobMap = findJob(id);
return ReadResponse.setSuccess().setData(jobMap).response();
}
/* NAVIGATION */
// Navigation from one person to one Job
// Example URI: srv/people(1)/Occupation
@Read(serviceName = "DemoService", entity = "Jobs", sourceEntity = "People")
public ReadResponse getJobForPerson(ReadRequest request) {
Integer personId = (Integer) request.getSourceKeys().get("PersonId");
Map<String, Object> personMap = findPerson(personId);
Integer jobId = (Integer) personMap.get("JobId");
Map<String, Object> jobMap = findJob(jobId);
return ReadResponse.setSuccess().setData(jobMap).response();
}
// Navigation from one job to all people who have this job.
// Example URI: /srv/Jobs(1)/Employees
@Query(serviceName = "DemoService", entity = "People", sourceEntity = "Jobs")
public QueryResponse getPeopleForJob(QueryRequest request) {
List<Map<String,Object>> responsePeopleList = new ArrayList<Map<String, Object>>();
Integer jobId = (Integer) request.getSourceKeys().get("JobId");
List<Map<String,Object>> allPeopleList = getAllPeople();
for(Map<String, Object> personMap : allPeopleList) {
if(((Integer)personMap.get("JobId")).equals(jobId)) {
responsePeopleList.add(personMap);
}
}
return QueryResponse.setSuccess().setDataAsMap(responsePeopleList).response();
}
// Navigation to collection and READ one entry
// Example URI: /srv/Jobs(1)/Employees(2)
@Read(serviceName = "DemoService", entity = "People", sourceEntity = "Jobs")
public ReadResponse getPersonForJob(ReadRequest request) {
//sourceEntity: Job
Integer jobId = (Integer) request.getSourceKeys().get("JobId");
// identify those people who have this job
List<Map<String,Object>> allPeopleList = getAllPeople();
List<Map<String,Object>> peopleForJobList = new ArrayList<Map<String, Object>>();
for(Map<String, Object> personMap : allPeopleList) {
if(((Integer)personMap.get("JobId")).equals(jobId)) {
peopleForJobList.add(personMap);
}
}
//now do the READ on this collection. First get the requested person-id from the request-URL
Integer requestedPersonId = (Integer) request.getKeys().get("PersonId");
// search for the requested person, but only in the collection of people, which we reached via navigation
for(Map<String, Object> person : peopleForJobList){
if (((Integer)person.get("PersonId")).equals(requestedPersonId)) {
// found the person
return ReadResponse.setSuccess().setData(person).response();
}
}
// Error: the person ID in the request (navigation target) must be wrong
return ReadResponse.setError(
ErrorResponse.getBuilder()
.setMessage("Error: Couldn't find requested person")
.setStatusCode(404)
.response());
}
// CREATE on a navigation property.
// Example URL: DemoService/Jobs(2)/Employees
@Create(serviceName = "DemoService", entity = "People", sourceEntity = "Jobs")
public CreateResponse createPersonForJob(CreateRequest request) {
// the value for "JobId" is retrieved from the request URI
Integer jobIdFromRequest = (Integer) request.getSourceKeys().get("JobId");
// other values are retrieved from the request body
Map<String, Object> requestBody = request.getMapData();
Integer idToCreate = (Integer) requestBody.get("PersonId");
String nameToCreate = (String) requestBody.get("Name");
// do the actual creation in database
Map<String, Object> createdPerson = createPerson(idToCreate, nameToCreate, jobIdFromRequest);
return CreateResponse.setSuccess().setData(createdPerson).response();
}
/* DUMMY DATABASE */
private List<Map<String, Object>> getAllPeople(){
if(peopleList.isEmpty()) {
createPerson(1, "Anna", 1);
createPerson(2, "Berta", 1);
createPerson(3, "Claudia", 2);
createPerson(4, "Debbie", 2);
}
return peopleList;
}
private Map<String, Object> createPerson(Integer personId, String name, Integer jobId){
Map<String, Object> personMap = new HashMap<String, Object>();
personMap.put("PersonId", personId);
personMap.put("Name", name);
personMap.put("JobId", jobId);
peopleList.add(personMap);
return personMap;
}
private Map<String, Object> findPerson(Integer requiredPersonId){
List<Map<String,Object>> peopleList = getAllPeople();
for(Map<String, Object> personMap : peopleList) {
if(((Integer)personMap.get("PersonId")).equals(requiredPersonId)) {
return personMap;
}
}
return null;
}
private List<Map<String, Object>> getAllJobs(){
if(jobList.isEmpty()) {
createJob(1, "Software Engineer");
createJob(2, "Musician");
createJob(3, "Architect");
}
return jobList;
}
private Map<String, Object> createJob(Integer jobId, String name){
Map<String, Object> jobMap = new HashMap<String, Object>();
jobMap.put("JobId", jobId);
jobMap.put("JobName", name);
jobList.add(jobMap);
return jobMap;
}
private Map<String, Object> findJob(Integer requiredJobId){
List<Map<String,Object>> jobList = getAllJobs();
for(Map<String, Object> jobMap : jobList) {
if(((Integer)jobMap.get("JobId")).equals(requiredJobId)) {
return jobMap;
}
}
return null;
}
}
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
User | Count |
---|---|
34 | |
25 | |
12 | |
7 | |
7 | |
6 | |
6 | |
6 | |
5 | |
4 |