In a previous post, we saw how to update data of OData v4 services with Olingo (see https://templth.wordpress.com/2014/12/05/manipulating-data-of-odata-v4-services-with-olingo/). In the real world, data are linked together. Its also the case with OData entities. In the latter post, we dont cover the way to manage these links.
We dont remind here how to configure Olingo in the project and to get an instance of the client. Please refer to the previous post for more details.
In this post, we use the demo OData v4 service provided by the platformservices.odata.org. This service provides read / write accesses and several kinds of relationships betweend entities.
As a context, the following link describes how OData v4 implements relationships between entities: http://www.asp.net/web-api/overview/odata-support-in-aspnet-web-api/odata-v4/entity-relations-in-odata-v4
Data model
The service we will use here contains several entities. Some of them have relationships. We will use three entities among them for this post and to describe how to manage links with the Olingo client:
- Category. The category of products.
- Supplier. The possible supplier for products.
- Product. The central one corresponding to a product. Its linked with a list of categories (property
Categories
) and with supplier (propertySupplier
).
We have the following categories in the service:
http://services.odata.org/V4/OData/(S(li4s4wcplcgyiy3uvuog1lxl))/OData.svc/Categories(0)
- ID = 0 (Edm.Int32)
- Name = Food (Edm.String)
http://services.odata.org/V4/OData/(S(li4s4wcplcgyiy3uvuog1lxl))/OData.svc/Categories(1)
- ID = 1 (Edm.Int32)
- Name = Beverages (Edm.String)
http://services.odata.org/V4/OData/(S(li4s4wcplcgyiy3uvuog1lxl))/OData.svc/Categories(2)
- ID = 2 (Edm.Int32)
- Name = Electronics (Edm.String)
and the following suppliers:
http://services.odata.org/V4/OData/(S(li4s4wcplcgyiy3uvuog1lxl))/OData.svc/Suppliers(0)
- ID = 0 (Edm.Int32)
- Name = Exotic Liquids (Edm.String)
(...)
http://services.odata.org/V4/OData/(S(li4s4wcplcgyiy3uvuog1lxl))/OData.svc/Suppliers(1)
- ID = 1 (Edm.Int32)
- Name = Tokyo Traders (Edm.String)
We will use the product with identifier 1
in the following:
http://services.odata.org/V4/OData/(S(li4s4wcplcgyiy3uvuog1lxl))/OData.svc/Products(1)
- ID = 1 (Edm.Int32)
- Name = Milk (Edm.String)
- Description = Low fat milk (Edm.String)
- ReleaseDate = 1995-10-01T00:00:00Z (Edm.DateTimeOffset)
- DiscontinuedDate = (Edm.String)
- Rating = 3 (Edm.Int16)
- Price = 3.5 (Edm.Double)
- Categories - Products(1)/Categories (ODataInlineEntitySet)
- link #1
- ID = 0 (Edm.Int32)
- Name = Food (Edm.String)
- link #2
- ID = 1 (Edm.Int32)
- Name = Beverages (Edm.String)
- Supplier - Products(1)/Supplier (ODataInlineEntity)
- link
- ID, 1 - Edm.Int32
- Name, Tokyo Traders - Edm.String
(...)
Different approaches
The first approach (we call it approach #1) consists in doing partial updates at the product level. It corresponds to provide the complete link field to update. As you guess, this can be very inefficient in the case of large lists of links. As a matter of fact, we overwrite the property itself and need to the service all the references.
The second one (we call it approach #2) deals with the navigation property link level itself within the Product level. We use add, update or delete operations according to cardinalities and dont need to know the entire content of the navigation properties.
Managing links between elements with single cardinality
Using the approach #1 simply consists in a classical update of a property of an entity. Rather than creating a primitive or complex property, we need to create a navigation link property with the method newEntityNavigationLink
of the class ObjectFactory
.
Following code describes how create the content to provide for the update:
ODataEntity productUpdates = client.getObjectFactory().newEntity(
new FullQualifiedName("ODataDemo", "Product"));
URI supplierRef = client.newURIBuilder(serviceRoot)
.appendEntitySetSegment("Suppliers")
.appendKeySegment(1).build();
ODataLink supplier = client.getObjectFactory()
.newEntityNavigationLink("Supplier", supplierRef);
productUpdates.getNavigationLinks().add(supplier);
The update request is executed as described below:
String serviceRoot = "http://localhost:8080/V4/OData/(S(li4s4wcplcgyiy3uvuog1lxl))/OData.svc";
URI productUri = client.newURIBuilder(serviceRoot)
.appendEntitySetSegment("Products")
.appendKeySegment(1)
.build();
ODataEntityUpdateRequest<ODataEntity> req = client.getCUDRequestFactory()
.getEntityUpdateRequest(productUri, UpdateType.PATCH, productUpdates);
ODataEntityUpdateResponse<ODataEntity> res = req.execute();
if (res.getStatusCode() == 204) {
System.out.println("Updated");
}
We can notice that unlinking entity isnt supported with this approach (i.e. set uri to null
).
For information, the following request with the following payload is executed:
PATCH /V4/OData/(S(li4s4wcplcgyiy3uvuog1lxl))/OData.svc/Products(1)
{
"@odata.type":"#ODataDemo.Product",
"Supplier@odata.bind":"http://localhost:8080/V4/OData/(S(li4s4wcplcgyiy3uvuog1lxl))/OData.svc/Suppliers(1)"
}
Managing links between elements with single cardinality
Updating a navigation link with Olingo with this approach isnt supported. In fact, using the request of type ODataReferenceAddingRequest
should be possible but it uses the HTTP method POST
under the hood whereas it should be an HTTP PUT
one in the case of a single cardinality.
Following code describes how to implement duché use case:
String serviceRoot = (...);
URI productSupplierUri = client.newURIBuilder(serviceRoot)
.appendEntitySetSegment("Products")
.appendKeySegment(1)
.appendNavigationSegment("Supplier")
.appendRefSegment()
.build();
URI supplierRef = client.newURIBuilder(serviceRoot)
.appendEntitySetSegment("Suppliers")
.appendKeySegment(2)
.build();
ODataReferenceAddingResponse addingRes = client
.getCUDRequestFactory()
.getReferenceAddingRequest(productSupplierUri, supplierRef)
.execute();
Moreover the link URI doesnt seem to be sent within the request. Here is the exchanged payload of such request and the received error:
POST /V4/OData/(S(li4s4wcplcgyiy3uvuog1lxl))/OData.svc/Products(1)/Supplier/$ref
HTTP/1.1 405 Method Not Allowed
{
"error":{
"code":"",
"message":"The URI (...) is not valid for POST operation. For POST operations, the URI must refer to a service operation or an entity set."
}
}
Such approach is however suitable to remove link (unlink) in the case of a single cardinality. We can use a classical delete request but with a ref
segment. This can be created using the method appendRefSegment
of the class URIBuilder
. The following code describes how to implement this:
URI productSupplierUri = client.newURIBuilder(serviceRoot)
.appendEntitySetSegment("Products")
.appendKeySegment(1)
.appendNavigationSegment("Supplier")
.appendRefSegment().build();
ODataDeleteResponse deleteRes = client.getCUDRequestFactory()
.getDeleteRequest(productSupplierUri).execute();
if (deleteRes.getStatusCode() == 204) {
// Deleted
}
For information, the following request with the following payload is executed:
DELETE /V4/OData/(S(li4s4wcplcgyiy3uvuog1lxl))/OData.svc/Products(1)/Supplier/$ref HTTP/1.1
Managing links between elements with multiple cardinality
With the approach #1 and with multiple cardinality, we need to provide the whole list of navigation link to update them.
The following code describes how to implement this:
String serviceRoot = (...);
URI productUri = client.newURIBuilder(serviceRoot)
.appendEntitySetSegment("Products")
.appendKeySegment(1)
.build();
ODataEntity productUpdates = client.getObjectFactory().newEntity(
new FullQualifiedName("ODataDemo", "Product"));
URI categoryRef = client.newURIBuilder(serviceRoot)
.appendEntitySetSegment("Categories")
.appendKeySegment(2)
.build();
ODataLink category = client.getObjectFactory()
.newEntitySetNavigationLink("Categories", categoryRef);
productUpdates.getNavigationLinks().add(category);
ODataEntityUpdateRequest<ODataEntity> req = client
.getCUDRequestFactory()
.getEntityUpdateRequest(productUri,
UpdateType.PATCH, productUpdates);
ODataEntityUpdateResponse<ODataEntity> res = req.execute();
We can notice the use of the method newEntitySetNavigationLink
instead of the one newEntityNavigationLink
in the case of a single cardinatlity.
For information, the following request with the following payload is executed:
PATCH /V4/OData/(S(li4s4wcplcgyiy3uvuog1lxl))/OData.svc/Products(1)
{
"@odata.type":"#ODataDemo.Product",
"Categories@odata.bind":["http://localhost:8080/V4/OData/(S(li4s4wcplcgyiy3uvuog1lxl))/OData.svc/Categories(2)"]
}
Managing links between elements with multiple cardinality
The feature should be supported since the corresponding request is available within the Olingo client API with the class ODataAddingReferenceRequest
:
String serviceRoot = (...);
URI productCategoriesUri = client.newURIBuilder(serviceRoot)
.appendEntitySetSegment("Products")
.appendKeySegment(1)
.appendNavigationSegment("Categories")
.appendRefSegment()
.build();
URI ref = client.newURIBuilder(serviceRoot)
.appendEntitySetSegment("Categories")
.appendKeySegment(1)
.build();
ODataReferenceAddingRequest req = client.getCUDRequestFactory()
.getReferenceAddingRequest(productCategoriesUri, ref);
ODataReferenceAddingResponse res = req.execute();
However there is a bug at this level since the specified navigation link isnt serialized within the request, as described below:
POST /V4/OData/(S(li4s4wcplcgyiy3uvuog1lxl))/OData.svc/Products(1)/Categories/$ref
HTTP/1.1 400 Bad Request
{
"error":{
"code":"",
"message":"An error occurred while processing this request.",
"innererror":{
"message":"An unexpected 'EndOfInput' node was found when reading from the JSON reader. A 'StartObject'
node was expected.","type":"Microsoft.OData.Core.ODataException","stacktrace":" "
}
}
}
We should normaly have this in the request:
POST /V4/OData/(S(li4s4wcplcgyiy3uvuog1lxl))/OData.svc/Products(1)/Categories/$ref
{"@odata.id":"http://(...)/Categories(1)"}
Removing entity links with multiple cardinality is similar than with single cardinality but we must specify the URI of the entity to remove from the list. Specifying this identifier can be done using the method id
of the class URIBuilder
. Following code describes how to implement this:
URI categoryRef = client.newURIBuilder(targetServiceRoot)
.appendEntitySetSegment("Categories")
.appendKeySegment(2).build();
URI productSupplierUri = client.newURIBuilder(serviceRoot)
.appendEntitySetSegment("Products")
.appendKeySegment(1)
.appendNavigationSegment("Categories")
.appendRefSegment()
.id(categoryRef.toString())
.build();
ODataDeleteResponse deleteRes = client.getCUDRequestFactory()
.getDeleteRequest(productSupplierUri).execute();
if (deleteRes.getStatusCode() == 204) {
// Deleted
}
Summary
Here is the summary on the way to manage links between entities with Olingo:
Feature | Single cardinality | Multiple cardinality |
Add / update ref (#1) | Yes | Yes |
Add / update ref (#2) | Buggy | Buggy |
Delete ref (#1) | No | No |
Delete ref (#2) | Yes | Yes |
I opened two issues in the Olingo JIRA regarding the problem I encountered when investigating the etity references with Olingo client.
Issue #1: there is a bug when using the adding reference request. The link of the reference to add isnt sent in the payload of the request.
- OLINGO-510
- Problem when using the
ODataReferenceAddingRequest
to add a reference to a property - https://issues.apache.org/jira/browse/OLINGO-510
Issue #2: there is no way to update a reference with cardinality 1 using $ref and a PUT method. Perhaps we should add an UpdatingReferenceRequest
.
- OLINGO-511
- Add a class
ODataReferenceUpdatingRequest
for reference with cardinality 1 - https://issues.apache.org/jira/browse/OLINGO-511