Updating data links of OData v4 services with Olingo

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 (property Supplier).

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.

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.

This entry was posted in Java, Olingo, REST and tagged , , , . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s