SLN Spotlight_SAP Labs India: ODATA Protocol implementation for IOT Applications on SAP HCP
This is a series of blog on Olingo OData implementation and divided into parts based on the topics, which I came across, while implementing the Olingo 2.0 APIs, First part consists of setting up of project and implementation of the Olingo OData APIs.
1. Introduction
This is series of documents on Apache Olingo OData2 services for HANA cloud platform (i.e. XSA and Cloud foundry) and JPA based web application. More details on creating JPA based application will be covered below. Apache Olingo is a Java library that implements the Open Data Protocol (OData). Apache Olingo serves client and server aspects of OData. It currently supports OData 2.0 for JPA Entities. Blog also provides different ways to customize pre/post processes for different type Requests on OData Services to various use cases, custom annotation, JPA mapping, entity mapping, debugging.
2. Prerequisite Activities
2.1 Create and build JPA Project
Web Project is already developed with JPA Entity to consume HANA Cloud Platform Database (HANA or MaxDB). Project should have persistence.xml, resource.xml and class files for JPA entities as shown in below picture.
2.2 Get dependent Library:
Add Following maven dependencies in project pom.xml to include OData Olingo and Apache CXF Jars
3. Configuration and Implementation
3.1 Configure persistence.xml in JPA model
Open persistence.xml file from /Java Resources/src/META-IN and Add the following yellow-marked line to the persistence.xml file
property name = “eclipselink.target-database” value = “org.eclipse.persistence.platform.database.HANAPlatform”
3.1.1 Configure resource.xml in JPA model
In resource.xml describes in cloud foundry, when application is pushed it will read the resource file about the HDI container to connect to Database, Here we have defined the provider, which can work with TOMEE-1.7 version or with tomcat8 (but we need to include the jar cxf-rt-frontend-jaxrs 2.7.8 version in pom.xml). Here service=sampleTest is the persistent unit name which has to define in web.xml, and JtaManaged is false, so that the JTA transaction will be maintained by Entity Manager factory.
3.2 Create Required Java Classes
3.2.1 Create Factory class for Managing Connection with Database using Server Context (eg. JpaEntityManagerFactory.java)
3.2.2 Create new Java class by extending ODataJPAServiceFactory to provide means for initializing Entity Data Model (EDM) Provider and OData JPA Processors.
3.3 Configure web descriptor file (web.xml)
Configure the web application as shown below by adding the following servlet configuration to web.xml. The Service factory (e.g. SampleCustomOdataJpaServiceFactory) which was implemented is configured in the web.xml of the OData Application as one of the init parameters.
4. Testing OData Service
Publish and start web application (Contain JPA Entity) on Cloud foundry, XSA or local web server and Open the application URL and append recourse path provided in web.xml for accessing OData Service,
Test Other OData operations using following tutoria l http://www.odata.org/getting-started/basic-tutorial/
As well as you can check the ODATA APIs using Advance rest client or Postman (Both are Chrome extension). You can test all the method like GET, PUT, POST, DELETE etc. using Advance rest Client.
5. Custom OData Processing
Most of the time in web project we need to provide custom pre and post processing with default OData processing like
- Adding additional filters or select filter to restrict usages of OData GET Request
- Manipulating results of GET Request
- Triggering some functionality in case of new Entity creation
- Filled non-provided data for Entity.
- Redefining metadata like renaming of any columns or any renaming any entity class name.
5.1 Create Custom JPA Processor for OData Request
Extend default implement class org.apache.olingo.odata2.jpa.processor.core.ODataJPAProcessorDefault for OData Processing handling with your own Custom Processor Class like e.g. Class CustomoDataJPAProcessor in following screen shot and override method for different operations on case by case basis
5.1.1. Override readEntitySet Method for extending GET request to get entitySet as return – The instance variable jpaProcessor can be used to process the OData request. The jpaProcessor returns the JPA entities after processing the OData request.
5.1.2. Override createEntity method for extending POST request to create new record for entity – create private method to manipulate entity record and add any additional processing, You can see other default method in default JPA Processor class ODataJPAProcessorDefault,
5.1.3 Use CustomODataServiceFactory for extending your service factory class instead of using default ODataJPAServiceFactory
Change extension for class created in step 3.2.
What is the Open Data Protocol (OData)? http://www.odata.org/
Why does SAP prefer OData as connectivity?
These two SCN blogs motivates the reasons why SAP prefers to use OData as connectivity:
Hope this blog covers the topic of setting up and implementation of OData services using Apache olingo API. In next part of my blog I will be explaining about the Entity class overview in JPA based web application.
I have checked in my code to github.
Few of my other blog post:-
Suggestions and questions are welcomed .
OData + RxJava + Retrofit 2 для android приложения
Столкнулся на проекте с проблемой доселе не виданной. Пришлось покурить документацию и в этой статье я расскажу как с помощью RxJava и Retrofit 2 — можно решить задачу по созданию клиента Odata для android приложения.
Спасибо огромное Jake Wharton за создание таких комфортных инструментов.
Добро пожаловать в мир магии
У нас есть приложение, которое по протоколу Odata должно выгребать с сервера данные, отображать их в списках, которые должны подгружаться по мере прокрутки и отправлять данные созданные пользователем на сервер. Тривиальная задача, но не тут то было, то что работает без проблемно на Java — не хочет так же работать с android.
А библиотеки и документация на Odata только от Apache — Olingo и Microsoft на C#.
В данной статье протокол Odata рассматривать я не буду, очень хорошая документация есть у Microsoft и ссылки я оставлю в конце статьи.
Вот вкратце определение с Wiki Open_Data_Protocol
Open Data Protocol (OData) — это открытый веб-протокол для запроса и обновления данных. Протокол позволяет выполнять операции с ресурсами, используя в качестве запросов HTTP-команды, и получать ответы в форматах XML или JSON.
Начиная с версии 4.0, OData — открытый стандарт, одобренный OASIS.
И вот здесь и начинается самое интересное, Odata — это своеобразный SQL в REST API и для динамически создаваемых данных самое то.
Но у нас строго типизированный язык и без знания модели — обработка и хранение данных создают довольно не простую задачу.
Решение которой не может быть типовым и многократно описанным в сети.
Скажу даже больше, кроме этих ссылок на документацию в сети — по теме все плохо.
Создаем службу для работы с сетью
Будем использовать Retrofit 2 и сопутствующие продукты компании Square
// Network implementation 'com.squareup.retrofit2:retrofit:2.7.1' // Собственно сам Retrofit 2 implementation 'com.squareup.retrofit2:converter-gson:2.7.1' // Конвертер для работы с JSON implementation 'com.squareup.okhttp3:logging-interceptor:4.3.1' // Перехватчик запросов implementation 'com.squareup.retrofit2:adapter-rxjava2:2.7.1' // Адаптер для работы с RxJava implementation 'com.squareup.okhttp3:okhttp:4.3.1' // OkHttp - это HTTP-клиент
Все эти зависимости и есть мощнейшая библиотека для работы с с сетью в Java.
Описывать работу с Retrofit 2 не вижу смысла, вот есть хороший мануал: Используем Retrofit 2 в Android-приложении.
public class NetworkService < private static final String TAG = "NetworkService"; private static final NetworkService mInstance = new NetworkService(); private String mToken; private Retrofit mRetrofit; public static NetworkService getInstance() < return mInstance; >private NetworkService() < RxJava2CallAdapterFactory rxAdapter = RxJava2CallAdapterFactory .createWithScheduler(Schedulers.io()); HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor(); interceptor.setLevel(HttpLoggingInterceptor.Level.BODY); OkHttpClient.Builder okHttpClient = new OkHttpClient.Builder() .addInterceptor(interceptor) .addInterceptor(chain ->< Request newRequest = chain.request().newBuilder() .addHeader("Accept", "application/json,text/plain,*/*") .addHeader("Content-Type", "application/json;odata.metadata=minimal") .addHeader("Authorization", mToken) .build(); return chain.proceed(newRequest); >); Gson gson = new GsonBuilder() .setDateFormat("yyyy-MM-dd'T'HH:mm:ssZ") .create(); mRetrofit = new Retrofit.Builder() .baseUrl(Const.BASE_URL) .addConverterFactory(GsonConverterFactory.create(gson)) .addCallAdapterFactory(rxAdapter) .client(okHttpClient.build()) .build(); > public ApiService getNetworkClient() < return mRetrofit.create(ApiService.class); >>
public interface ApiService < // Делаем запрос без условий @GET("odata/product") Observable> getProducts(); >
public class ProductsController < private ApiService mApiService; private ListlistProducts; private Gson gson; public ProductsController() < mApiService = App.getNetworkService().getNetworkClient(); listProducts = new ArrayList<>(); gson = new Gson(); > public void productsBtnClick() < mApiService.getProducts() .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new DisposableObserver() < @Override public void onNext(Listproducts) < listProducts.addAll(listProducts); >@Override public void onError(Throwable e) < >@Override public void onComplete() < >>); >
Итак, на сервере храниться модель данных Poduct, которая имеет какие то параметры и атрибуты. Все данные идут в формате JSON и для работы нам необходим POJO класс.
Рекомендую в HttpLoggingInterceptor настроить уровень подробности перехвата — Level.BODY.
Делаем запрос listing 4, чтобы максимально вытянуть структуру данных и ответ будет приблизительно в таком формате:
// Есть некий список в котором перечислены сущности высшего уровня // в формате JSON < "@odata.context":"https://example.xxx/api/odata/$metadata","value":[ < "name":"product","kind":"EntitySet","url":"product" // продукт >,< "name":"blogs","kind":"EntitySet","url":"blogs" // блоги >,< "name":"posts","kind":"EntitySet","url":"posts" // посты >,< "name":"comments","kind":"EntitySet","url":"comments" // комментарии >, < "name":"rates","kind":"EntitySet","url":"rates" // рейтинги >] >
И уже на основании этих данных можно формировать запрос для дальнейших манипуляций с данными.
То есть это полноценный объект с каким то поведением и атрибутами, чтобы получить эту информацию при создании запроса необходимо добавить условия, на основании которых мы и получим наш DataSource не выдумывая новый велосипед и не прикручивая к нему костыли.
Кульминация и щенячий восторг от комфорта инструмента
И вот он момент истинны, мощь, сила и простота библиотеки Retrofit 2. Теперь можно получить properties используя сервисный документ Odata:
// Можно получить properties сущности product @GET("odata/product?$filter=Id eq 111&$expand=dateReading($orderby=Date desc") Observable> getProducts(); // Или properties сущности blogs @GET("odata/blogs?$orderby=Date desc") Observable> getBlogs(); // И запрос уже можно формулировать исходя из потребностей @GET("odata/product?$filter=((Id eq 19) and (Name eq 'Available')) and ((Status eq 'OPEN') or ((Status eq 'CLOSED') and (Date ge 2020-02-13T06:39:48Z)))&$orderby=Status asc,Date desc&$top=10&$expand=AuthorId,CategoryId($expand=weight)&$count=true") Observable> getProducts(); // Этот запрос выдаст точную информацию отобранную по условиям, // но он явно выходит за разумные рамки. // Исправляем: @GET Observable> getProducts(@Url String url);
Вот она силушка богатырская библиотеки Retrofit 2. Динамический Url забирает на себя всю эту массу параметров, с которыми можно играться в коде на сколько хватит фантазии.
private void buttonGetProduct() < // Здесь со строками можно все String one = "odata/product?$filter=Id eq "; String ; String tree = "&$expand=dateReading($orderby=Date desc)"; String url = one + id + tree; mApiService.getProduct(url) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new DisposableObserver>() < @Override public void onNext(Listproducts) < // А вот здесь мы и получаем искомое, правда данные в формате JSON, // но есть масса конвертеров и это не проблема mProductsDto.addAll(countersDtos); >@Override public void onError(Throwable e) < >@Override public void onComplete() < >>); >
Итоги
Это был полезный опыт, которым я и спешу поделится, мне в свое время эта статья реально бы убрала кучу проблем и вопросов.
В статье я не стал углубляться в ненужные подробности по Retrofit 2 и OData и указал ссылки на документацию если возникнет необходимость вникнуть глубже.
Полную версию кода предоставить не могу, за что приношу извинения, продукт коммерческий.