Accessing data of OData v4 services with Olingo

Olingo (http://olingo.apache.org) is a framework that implements the OData specification. Its provides support for its latest version, the version 4, for both client and server sides. We will describe here how to use the client side to retrieve data.

Configure Olingo in the project

The simplest way to configure the Olingo client to interact with OData v4 services is to use Maven and define the client as a dependency in the file pom.xml, as described below:

<?xml version="1.0" encoding="UTF-8"?>
<project (...)>
    <modelVersion>4.0.0</modelVersion>
    (...)
    <dependencies>
        <dependency>
            <groupId>org.apache.olingo</groupId>
            <artifactId>odata-client-core</artifactId>
            <version>4.0.0-beta-01</version>
        </dependency>
    </dependencies>
   (...)
</project>

Maven can be used then to generate the configuration for your IDE. For example, for Eclipse, simply execute the following command:

mvn eclipse:eclipse

Now we have a configured project, lets start to implement the processing. We will focus here on the way to retrieve data. We will base on the online service Northwind. We first describe the general frame to build a request. We can then go further and build more complex requests and describe how to navigate into data.

Getting an instance of the client

Getting the Olingo client instance is pretty simple using the factory class ODataClientFactory, as described below:

ODataClient client = ODataClientFactory.getV4();

On our case, we choose a OData v4 compliant client. This instance will be used to execute requests and interact with the OData service.

Getting service metadata

OData provides service metadata to determine which data can be used for the service. Olingo provides the class ODataServiceDocumentRequest, ODataRetrieveResponse to execute a request against the service document. The following code describes how to use them:

String serviceRoot = "http://services.odata.org/V4/Northwind/Northwind.svc";
ODataServiceDocumentRequest req =
client.getRetrieveRequestFactory().getServiceDocumentRequest(serviceRoot);
ODataRetrieveResponse<ODataServiceDocument> res = req.execute();

From this response, we can get an instance of class ODataServiceDocument to actually get the metadata, as described below:

ODataServiceDocument serviceDocument = res.getBody();

Collection<String> entitySetNames = serviceDocument.getEntitySetNames();
Map<String,URI> entitySets = serviceDocument.getEntitySets();
Map<String,URI> singletons = serviceDocument.getSingletons();
Map<String,URI> functionImports = serviceDocument.getFunctionImports();
URI productsUri = serviceDocument.getEntitySetURI("Products");

At this level, we can only have access to elements provided and their URIs but we can have details of their structures. If we want to do a deeper introspection, we need to use a metadata request, as described below:

String serviceRoot = "http://services.odata.org/V4/Northwind/Northwind.svc";
EdmMetadataRequest request
   = client.getRetrieveRequestFactory().getMetadataRequest(serviceRoot);
ODataRetrieveResponse<Edm> response = request.execute();

Fomr the response, we get an instance of the class Edm that contains all the metadata. We can first begin to get the schemas, get all contained elements. Lets see below how to implement this:

Edm edm = response.getBody();

List<EdmSchema> schemas = edm.getSchemas();
for (EdmSchema schema : schemas) {
    String namespace = schema.getNamespace();
    for (EdmComplexType complexType : schema.getComplexTypes()) {
        FullQualifiedName name = complexType.getFullQualifiedName();
    }
    for (EdmEntityType entityType : schema.getEntityTypes()) {
        FullQualifiedName name = entityType.getFullQualifiedName();
    }
}

We can get now details about the element itself. The following code gets details of the entity Customers:

EdmEntityType customerType = edm.getEntityType(
        new FullQualifiedName("NorthwindModel", "Customer"));
List<String> propertyNames = customerType.getPropertyNames();
for (String propertyName : propertyNames) {
    EdmProperty property = customerType.getStructuralProperty(propertyName);
    FullQualifiedName typeName = property.getType().getFullQualifiedName();
}

Now we have inspected the metadata of the service, we can actually get the corresponding data.

Getting data

Getting data with Olingo consists in several steps:

  • Build the request URI
  • Execute the request
  • Browse the received data

For the next step, Olingo provides the class URIBuilder that allows to build a OData v4 compliant URI for data we want to get. This class supports all features of OData v4. We leverage it all along the post to build our queries. Following code describes how to use it to create an URI to get all customers:

String serviceRoot = "http://services.odata.org/V4/Northwind/Northwind.svc";
URI customersUri = client.newURIBuilder(serviceRoot)
                 .appendEntitySetSegment("Customers").build();

Now we have the URI, we can execute the request using the client instance. Olingo gives access then to an iterator to browse the received data. Following code describes how to get data from this instance:

ODataRetrieveResponse<ODataEntitySetIterator<ODataEntitySet, ODataEntity>> response
     = client.getRetrieveRequestFactory().getEntitySetIteratorRequest(customersUri).execute();

ODataEntitySetIterator<ODataEntitySet, ODataEntity> iterator = response.getBody();

Now we received the data, we can browse them based on the iterator. The latter contains elements of type ODataEntity. Each element has the following

  • Properties: the properties of entities
  • Navigation links: the links that allow to navigate through the data graph from these entities. This includes inline entities and entity sets
  • Association links: the navigation links
  • Operations: the operations that can be executed on entities

Following code describes how to iterate over customers and get their properties:

while (iterator.hasNext()) {
    ODataEntity customer = iterator.next();
    List<ODataProperty> properties = customer.getProperties();
    for (ODataProperty property : properties) {
        String name = property.getName();
        ODataValue value = property.getValue();
        String valueType = value.getTypeName();
        (...)
    }
}

We will describe navigation links below in the section .

Controlling retrieved data

The class URIBuilder allows to specify the subset of properties to get for entities with its method select. In the following code, we only want to get the properties PersonID, CompanyName and ContactName:

URI customersUri = client.newURIBuilder(serviceRoot)
                 .appendEntitySetSegment("Customers")
                 .select("PersonID,CompanyName,ContactName").build();

Counting elements and handling pagination

The method count of the class URIBuilder specifies that the request corresponds to a count of elements. Building an URI for a count request is described below:

URI customersCountUri = client.newURIBuilder(serviceRoot)
                 .appendEntitySetSegment("Customers")
                 .count(true).build();

Getting the count result can be simply done on the body of the response since the class ODataEntitySet provides a method getCount, as described below:

ODataEntitySet entitySet = req.execute().getBody();
int count = entitySet.getCount();

In the context of pagination, the number of elements is useful to determine the number of pages for the result. We can then play with arguments top for the maximum number of elements to retrieve per request and skip to specify the number of elements to be skipped at the beginning of result. For example, to retrieve the third page of data, we can simply

int page = 3;
int numberOfElementsPerPage = 10;
URI customersUri = client.newURIBuilder(serviceRoot)
                 .appendEntitySetSegment("Customers")
                 .top((page-1)*numberOfElementsPerPage).skip(10).build();

We can also notice the method skipToken to get elements with identifier greater than the value specified. An example of use is described below:

URI customersUri = client.newURIBuilder(serviceRoot)
                 .appendEntitySetSegment("Customers")
                 .skipToken("5").build();

Ordering data

Ordering data is simply based on the method orderBy of the class URIBuilder, as described below. We can notice that its possible to possible to specify if we want to order ascending or descending.

URI customersUri = client.newURIBuilder(serviceRoot)
                 .appendEntitySetSegment("Customers")
                 .orderBy("PersonID desc").build();

Browsing the data graph with links

OData supports data graph and handle relationships between elements. By default, only the first level of the graph are retrieved. If relationships are present, the elements contain references to other levels.

Following code describes how to get to levels of the data graph with two requests.

  • The first one for the customers
  • The second one for the orders linked to customers

Following code describes how to follow navigation links in order to get corresponding data:

ODataEntity customer = iterator.next();
List<ODataLink> links = customer.getNavigationLinks();
for (ODataLink link : links) {
    URI linkUri = client.newURIBuilder(serviceRoot)
              .appendNavigationSegment(link.getLink().toString()).build();
    ODataRetrieveResponse<ODataEntitySetIterator<ODataEntitySet, ODataEntity>> responseOrders
            = client.getRetrieveRequestFactory().getEntitySetIteratorRequest(linkUri).execute();
    ODataEntitySetIterator<ODataEntitySet, ODataEntity> iteratorOrders = responseOrders.getBody();

    while (iteratorOrders.hasNext()) {
        ODataEntity order = iteratorOrders.next();
        List<ODataProperty> propertiesOrder = order.getProperties();
        for (ODataProperty propertyOrder : propertiesOrder) {
            ODataValue value = propertyOrder.getValue();
            (...)
        }

        (...)
    }
}

For information, the value contained in the link is Customers('ANATR')/Orders.

Its possible to do the same thing in only one request. This can be configured using the method expand of the class URIBuilder. Its parameter defines the name of the field to load eagerly.

In this context, Olingo provides methods expand and expandWithSelect on the class URIBuilder. They allow to specify the properties to load in the same request, as described below:

URI customersWithOrdersUri = client.newURIBuilder(serviceRoot)
                 .appendEntitySetSegment("Customers")
                 .select("PersonID,Orders").expand("Orders").build();

Now we saw how to configure the request, lets have Ć  look at the way to extract corresponding data from rachat element in the response body. In such case, the link becomes . This means that the data are there and can be directly gotten. To do this, simply cast the instance of OdataLink to ODataInlineEntitySet. This gives us access to the corresponding entry set and the list of entities itself.

ODataEntity customer = iterator.next();
List<ODataLink> links = customer.getNavigationLinks();
for (ODataLink link : links) {
    ODataInlineEntitySet inlineLink = (ODataInlineEntitySet) link;
    CommonODataEntitySet entitySet = inlineLink.getEntitySet();
    List<ODataEntity> entities = (List<ODataEntity>) entitySet.getEntities();
    for (ODataEntity entity : entities) {
        List<ODataProperty> entityProperties = entity.getProperties();
        for (ODataProperty property : entityProperties) {
            ODataValue value = property.getValue();
            (...)
        }
        (...)
    }
}

The previous sample describes the use of a link with multiple cardinality. Such mechanism is also usable with cardinality of 1. The main difference is the way to get corresponding data. As a matter of fact, we dont retrieve an entity set in this case, but directly an entity. We need to cast to the class ODataInlineEntity, as described below:

List<ODataLink> links = customer.getNavigationLinks();
for (ODataLink link : links) {
    ODataInlineEntity inlineLink = (ODataInlineEntity) navigationLink;
    CommonODataEntity entity = inlineLink.getEntity();
    List<ODataProperty> entityProperties
           = (List<ODataProperty>) entity.getProperties();
    for (ODataProperty property : entityProperties) {
        ODataValue value = property.getValue();
        (...)
    }
    (...)
}

Loading data by identifier

We can also get an element by its identifier rather than finding it from a query. In the case, we need to use the method appendKeySegment of the class URIBuilder, as described below:

URI customerUri = client.newURIBuilder(serviceRoot)
                 .appendEntitySetSegment("Customers")
                 .appendKeySegment(''ERNSH").build();

In this context, we get only one element and not a list of elements. We need to adapt the request execution to specify that we expect only one element. We use in this case the method getEntityRequest as described below. The body of the response is the element itself.

ODataRetrieveResponse<ODataEntity> response =
client.getRetrieveRequestFactory().getEntityRequest(customersUri).execute();

ODataEntity customer = response.getBody();

Searching OData v4 data

OData provides the parameter $filter allows to execute query on data. The method filter of the class URIBuilder accepts as parameter the expression to evaluate, as described below:

URI customersUri = client.newURIBuilder(serviceRoot)
          .appendEntitySetSegment("Customers")
          .filter("CustomerID eq 'ERNSH' or CustomerID eq 'ALFKI'")
          .build();

We dont describe here all the expression capabilities supported for the filter expression but such expressions support operators (logical and comparison, arithmetic, lambda), canonical functions, path exdpressions and grouping. This allows to build complex search expressions.

For more details, see the section > in the part 2 of the OData v4 specification.

Another way to do search with OData consists in using the parameter $search allows to implement free-text search. The method search of the class URIBuilder used jointly with the class SearchFactory allows to use this feature, as described below:

URI customersUri = client
          .newURIBuilder(serviceRoot)
          .appendEntitySetSegment("Customers")
          .search(client.getSearchFactory().or(
               client.getSearchFactory().literal("blue"),
               client.getSearchFactory().literal("green"))
          ).build();

It is up to the service to decide what makes a product blue or green. For this reason, this parameter isnt always supported by services.

Controlling data format

OData v4 supports two format to exchange data: JSON (the default one) and Atom. Olingo allows to control which format is used under the hood. There are two ways to do this:

  • The first one leverages the parameter $format of OData
  • The second one is based on the HTTP header Accept.

The difference between these two approaches is that the second one goes further and also allows to control the metadata exhanged.

The first approach uses the method format of the class URIBuilder, as described below:

URI customersUri = client.newURIBuilder(serviceRoot)
            .appendEntitySetSegment("Customers").format("atom").build();

The second approach is based on the class ODataFormat and the method setFormat of the request instance. The class defines a set of constant to specify what we actually expect regarding the response content (format and level of metadata in the case of JSON). Following code describes how to implement such approach:

URI customersUri = client.newURIBuilder(serviceRoot)
             .appendEntitySetSegment("Customers").build();
ODataRetrieveRequest<ODataEntitySetIterator<ODataEntitySet, ODataEntity>> request
      = client.getRetrieveRequestFactory().getEntitySetIteratorRequest(customersUri);
//request.setFormat(ODataFormat.JSON_NO_METADATA);
request.setFormat(ODataFormat.ATOM);
ODataRetrieveResponse<ODataEntitySetIterator<ODataEntitySet, ODataEntity>> response
       = request.execute();

To finish, lets have a look at the way to handle errors that can occur requests.

Handle errors

When something wrong happens during a call, the Olingo client will throw an exception of type ODataClientErrorException. We simply need to catch it and handle the corresponding error. In the following sample, the entity Customers1 doesnt exist:

URI customersUri = client.newURIBuilder(serviceRoot)
         .appendEntitySetSegment("Customers1").build();

try {
    ODataRetrieveResponse<ODataEntitySetIterator<ODataEntitySet, ODataEntity>> response
             = client.getRetrieveRequestFactory().getEntitySetIteratorRequest(customersUri).execute();
    (...)
} catch(ODataClientErrorException ex) {
    ex.printStackTrace();
}

The exception contains the following message:

org.apache.olingo.client.api.communication.ODataClientErrorException: Resource not found for
the segment 'Customers1'. [HTTP/1.1 404 Not Found]

We also have the ability to check the status code returned and use it to handle errors:

ODataRetrieveResponse<ODataEntitySetIterator<ODataEntitySet, ODataEntity>> response
        = client.getRetrieveRequestFactory().getEntitySetIteratorRequest(customersUri).execute();

if (response.getStatusCode() >= 400) {
    // Handle error
}

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

6 Responses to Accessing data of OData v4 services with Olingo

  1. Pingback: Manipulating data of OData v4 services with Olingo | Sandbox for the Web stack

  2. Henri says:

    Very good explanation! Works fine for me!!!!

  3. Hass says:

    Great tutorial ! It’s very helpfull :-)! However I have one question. How can I use “expandWithQuery” method to build a request and make “imbricated expand” ? I’m facing some issue during execution that I don’t understand …

    • templth says:

      Thanks vey much for your feedback!

      I think that you look for this:

      Map options = new HashMap();
      options.put(QueryOption.FILTER, “ShipName eq ‘Alfreds Futterkiste'”);

      URI customersUri = client.newURIBuilder(serviceRoot)
      .appendEntitySetSegment(“Customers”)
      .select(“CustomerID,Orders”)
      .expandWithOptions(“Orders”, options).build();

      This allows to build something as described in section “Nested Filter in Expand” of the link http://www.odata.org/getting-started/basic-tutorial/.

      • Hass says:

        Thanks very much! That’s exactly what I’m looking for but I need to do this:

        options.put(QueryOption.EXPAND, ā€œ….ā€);

        And then my application crashes … 😦

        Any help ?

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 )

Connecting to %s