How to consume an OData Service of SAP NetWeaver Gateway with Apache Olingo
This article shows how to consume (Read) an OData Service with support of the Apache Olingolibrary.
As OData Service a gateway demo service is used which defines a data model containingBusinessPartners and their related Products they sell. For this sample we assume an use case that a client (e.g. UI) is interested in all offered Products and in addition the related BusinessPartner which supplies the Product.
To implement and demonstrate the described use case following steps have to be proceeded:
- Fulfil prerequisites to access the gateway OData demo service
- Read a single Product and Read all available Products
- Navigation from a Product to the related BusinessPartner
- Inclusion of information of related Entries to reduce requests
- Selection of wanted information from Entries to reduce transfered data
Prerequisites
The gateway demo service is named ZGWSAMPLE_SRV and the functionality the service provides is described here. To get access to the gateway demo service it is necessary to sign up as described.
Read
Apache Olingo provides client functionality in form of create Requests (serialization) and interpret Responses (deserialization) based on the OData format. It does not provide functionality for sending and receiving requests so an application is free to decide whatever HTTP client library it wants to use. The methods to access provided serialization and deserialization functionality are all declared at the EntityProvider
class. For basic consumption (Read) of an OData Service following methods are necessary:
EntityProvider.readMetadata(...)
to read the metadata of the service.EntityProvider.readEntry(...)
to read an single entry.EntityProvider.readFeed(...)
to read a collection of entities.
Read Prerequisites: EDM/$metadata
Because Apache Olingo requires the metadata for serialization and deserialization of an entity the first step is to read the whole Entity Data Model (EDM) of an OData Service.
public Edm readEdm(String serviceUrl) throws IOException, ODataException { InputStream content = execute(serviceUrl + "/" + METADATA, APPLICATION_XML, HTTP_METHOD_GET); return EntityProvider.readMetadata(content, false); }
To read the Entity Data Model (EDM) a HTTP GET on the corresponding $metadata URI of the OData Service via the execute(...)
method. The resulting content is passed into theEntityProvider.readMetadata(InputStream content, boolean validate)
method which de-serialize the EDMX into an EDM
object. This EDM
object than can be used for necessary deserialization in combination of read operations.
Read Entity: Product
To read a single Product of the service a single request to the ProductsCollection URI with set ID is done.
public ODataEntry readEntry(Edm edm, String serviceUri, String contentType, String entitySetName, String keyValue) throws IOException, ODataException { // working with the default entity container EdmEntityContainer entityContainer = edm.getDefaultEntityContainer(); // create absolute uri based on service uri, entity set name and key property value String absolutUri = createUri(serviceUri, entitySetName, keyValue); InputStream content = execute(absolutUri, contentType, HTTP_METHOD_GET); return EntityProvider.readEntry(contentType, entityContainer.getEntitySet(entitySetName), content, EntityProviderReadProperties.init().build()); }
The execute(...)
method executes the request against the absolute URI and returns the response content as InputStream
which then is deserialized by the EntityProvider.readEntry(...)
into anODataEntry
object. This contains all properties (data) of the entry in form of a Map, additional entry metadata as EntryMetadata
object (which contains etag, id, association uris and uri information), the ExpandSelectTreeNode
, information whether the entry contains inline entries and if the entry is a media resource additional the MediaMetadata
.
Read Entities: ProductCollection
To read all Products of the service a single request to the ProductsCollection URI is done.
public ODataFeed readFeed(Edm edm, String serviceUri, String contentType, String entitySetName) throws IOException, ODataException { EdmEntityContainer entityContainer = edm.getDefaultEntityContainer(); String absolutUri = createUri(serviceUri, entitySetName, null); InputStream content = (InputStream) connect(absolutUri, contentType, HTTP_METHOD_GET).getContent(); return EntityProvider.readFeed(contentType, entityContainer.getEntitySet(entitySetName), content, EntityProviderReadProperties.init().build()); }
The execute(...)
method executes the request against the absolute URI and returns the response content as InputStream
which then is deserialized by the EntitProvider.readFeed(...)
into anODataFeed
object. This contains all entities provided by the OData Service as ODataEntry
in a list as well as FeedMetadata
like inline count and next link.
All used URIs
- Read metadata of service
- Read single Product with id "HT-1000" of service
- Read all Products of service
Navigate
The demo service defines an association between Products and their releated BusinessPartners as navigation property at a Product with the name Supplier. By following that association theBusinessPartner can be retrieved. This can be done by calling the absolute URI to a Supplier of a single Product which can be get by using the getAssociationUris(...)
method of theEntryMetadata
object. In this example the metadata object method is called with parameterSupplier
which then returns with a list which contains the absolute URI (e.g. https://sapes1.sapdevcenter.com/sap/opu/odata/sap/ZGWSAMPLE_SRV/ProductCollection('HT-1000')/Supplier) which can be called to get the Supplier for the Product.
For this example we prefer to already include the Supplier information for a Product into the response of our request. Therefore a read with expand system query option is the way to go (in next section).
All used URIs
Read more with Expand
To read an Entry or Feed which already includes properties of a related Entry (or Entries) the$expand
system query option can be used. The $expand
clause is a list off all associations to be retrieved within this request. For the example the $expand
parameter is set to the navigation property Supplier to be retrieved. This results in the following relative URI:"./ProductCollection('HT-1000')/?$expand=Supplier".
The client has only to append the $expand
for this case and can then again parse the result viaEntityProvider.readEntry(...)
. The difference now is that containsInlineEntry()
is true
and the Supplier properties are available as ODataEntry
within the properties (i.e. Map
) of the Product. To visualize the Map
looks like:
Product (as ODataEntry) \- *additional information like EntryMetadata* \- *all simple properties* \- Supplier (as ODataEntry) \- *additional information like EntryMetadata* \- *all simple properties*
The above shown readEntry(...)
method could be used with minor adaption of URI creation, which now has to include the name of the expanded navigation property.
String absolutUri = createUri(serviceUri, entitySetName, keyValue, expand);
All used URIs
Read more with Expand on a Feed
As mentioned in the section above the $expand
can also be used to read a Feed so that then as example each Product already includes the related Supplier. Also EntityProvider.readFeed(...)
can be used for deserialization and the only adaption in compare to normal read Feed use case is to append the $expand
system query option. For the sample use case the relative URI is"../ProductCollection/?$expand=Supplier".
All used URIs
Read less with Select
Each response usually contains all properties for an entry but this is not necessary for each client use case. Hence, for a use case in that a client is only interested in some properties it is possible to define the system query option $select
to specify which properties of an Entry should be sent in the response payload. As example we only want the Name of each Product and the CompanyName of the related Supplier. Then we can use the $expand
to include the Supplier in the response and define the $select
system query option with Name,Supplier/CompanyName
which results in the relative URI "../ProductCollection/?$select=Name,Supplier/CompanyName&$expand=Supplier".
Again the EntityProvider.readFeed(...)
method is used for deserialization and the only adaption in comparison to the read Feed with expand use case is appending the $select
system query option in creation of the URI.
String absolutUri = createUri(serviceUri, entitySetName, keyValue, expand, select);
All used URIs
Conclusion
In the sections above it was shown that with the support of Apache Olingo it is relatively easy for a client to consume an OData Service. While the library takes care of serialization and deserialization of the OData formatted entities the client can focus on processing the data.
Runnable sample client
A sample client exists for a quick Hands-On covering the here shown read use cases and in addition containing the boiler plate code which is necessary for the URL connection handling (e.g. Proxy/Authentication support).
To run this sample follow these steps:
- Prerequisite: An installed JVM (Version 1.6 or above) and Maven (Version 3 or above).
- Download it here.
- Extract it into some folder
- Configure proxy and/or authentication in
client.properties
in foldersrc/main/resources
(with credentials provided via signing up) - Execute
mvn compile exec:java
to run Maven which compiles and executes the sample project.