Olingoを使用したODataの概要

1. 前書き

このチュートリアルは、https://www.baeldung.com/odata [OData Protocol Guide]のフォローアップです。ここでは、https://www.odata.org/ [OData]プロトコルの基本を調査しました。
*これで、https://olingo.apache.org [Apache Olingo]ライブラリを使用して簡単なODataサービスを実装する方法がわかります*。
このライブラリは、ODataプロトコルを使用してデータを公開するためのフレームワークを提供するため、内部データベースではロックされている情報への標準ベースの簡単なアクセスが可能になります。

2. オリンゴとは?

  • Olingoは、Java環境で使用可能な「機能」OData実装の1つです。もう1つはhttps://github.com/sdl/odata[SDL OData Framework]です。 Apache Foundationによって管理され、3つの主要なモジュールで構成されています。

  • Java V2 – OData V2をサポートするクライアントおよびサーバーライブラリ

  • Java V4 – OData V4をサポートするサーバーライブラリ

  • Javascript V4 – Javascript、OData V4をサポートするクライアント専用ライブラリ

    *この記事では、JPAとの直接統合をサポートするサーバー側のV2 Javaライブラリのみを取り上げます*。 結果のサービスは、CRUD操作と、順序付け、ページング、フィルタリングなどのその他のODataプロトコル機能をサポートします。
    一方、Olingo V4は、コンテンツタイプネゴシエーションやURL解析など、プロトコルの低レベルの側面のみを処理します。 つまり、開発者は、メタデータの生成、URLパラメーターに基づくバックエンドクエリの生成などに関する詳細をすべてコーディングする必要があります。
    JavaScriptクライアントライブラリについては、ODataはHTTPベースのプロトコルであるため、任意のRESTライブラリを使用してアクセスできるため、ここでは省略します。

3. Olingo Java V2サービス

link:/odata [プロトコルの簡単な紹介]自体で使用した2つの__EntitySet__sを使用して、簡単なODataサービスを作成しましょう。 中核となるOlingo V2は、単にJAX-RSリソースのセットであるため、使用するために必要なインフラストラクチャを提供する必要があります。 つまり、JAX-RS実装と互換性のあるサーブレットコンテナが必要です。
この例では、* Spring Boot *を使用することを選択しました。これは、サービスをホストするのに適した環境をすばやく作成する方法を提供するためです。 また、OlingoのJPAアダプタを使用します。これは、ODataの_EntityDataModel。
厳密な要件ではありませんが、JPAアダプターを含めると、サービスを作成するタスクが大幅に簡素化されます。
標準のSpring Boot依存関係に加えて、Olingoのjarファイルをいくつか追加する必要があります。
<dependency>
    <groupId>org.apache.olingo</groupId>
    <artifactId>olingo-odata2-core</artifactId>
    <version>2.0.11</version>
    <exclusions>
        <exclusion>
            <groupId>javax.ws.rs</groupId>
            <artifactId>javax.ws.rs-api</artifactId>
         </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>org.apache.olingo</groupId>
    <artifactId>olingo-odata2-jpa-processor-core</artifactId>
    <version>2.0.11</version>
</dependency>
<dependency>
    <groupId>org.apache.olingo</groupId>
    <artifactId>olingo-odata2-jpa-processor-ref</artifactId>
    <version>2.0.11</version>
    <exclusions>
        <exclusion>
            <groupId>org.eclipse.persistence</groupId>
            <artifactId>eclipselink</artifactId>
        </exclusion>
    </exclusions>
</dependency>
これらのライブラリの最新バージョンは、Mavenの中央リポジトリで入手できます。
  • _https://search.maven.org/search?q = a:olingo-odata2-core [olingo-odata2-core] _

  • _https://search.maven.org/search?q = a:olingo-odata2-jpa-processor-core [olingo-odata2-jpa-processor-core] _

  • _https://search.maven.org/search?q = a:olingo-odata2-jpa-processor-ref [olingo-odata2-jpa-processor-ref] _

    OlingoはJPAプロバイダーとしてEclipseLinkに依存しており、Spring Bootとは異なるJAX-RSバージョンも使用しているため、このリストにこれらの除外が必要です。

3.1. ドメインクラス

OlingoでJPAベースのODataサービスを実装する最初のステップは、ドメインエンティティを作成することです。 この単純な例では、1対多の関係を持つ2つのクラス(_CarMaker_および_CarModel_)のみを作成します。
@Entity
@Table(name="car_maker")
public class CarMaker {
    @Id @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Long id;
    @NotNull
    private String name;
    @OneToMany(mappedBy="maker",orphanRemoval = true,cascade=CascadeType.ALL)
    private List<CarModel> models;
    // ... getters, setters and hashcode omitted
}

@Entity
@Table(name="car_model")
public class CarModel {
    @Id @GeneratedValue(strategy=GenerationType.AUTO)
    private Long id;

    @NotNull
    private String name;

    @NotNull
    private Integer year;

    @NotNull
    private String sku;

    @ManyToOne(optional=false,fetch=FetchType.LAZY) @JoinColumn(name="maker_fk")
    private CarMaker maker;

    // ... getters, setters and hashcode omitted
}

3.2. _ODataJPAServiceFactory_の実装

  • JPAドメインからデータを提供するためにOlingoに提供する必要がある主要なコンポーネントは、ODataJPAServiceFactory . *と呼ばれる抽象クラスの具体的な実装です。このクラスは_ODataServiceFactory_を拡張し、JPAとOData間のアダプターとして機能します。 ドメインのメイントピックにちなんで、このファクトリーに_CarsODataJPAServiceFactory_という名前を付けます。

@Component
public class CarsODataJPAServiceFactory extends ODataJPAServiceFactory {
    // other methods omitted...

    @Override
    public ODataJPAContext initializeODataJPAContext() throws ODataJPARuntimeException {
        ODataJPAContext ctx = getODataJPAContext();
        ODataContext octx = ctx.getODataContext();
        HttpServletRequest request = (HttpServletRequest) octx.getParameter(
          ODataContext.HTTP_SERVLET_REQUEST_OBJECT);
        EntityManager em = (EntityManager) request
          .getAttribute(EntityManagerFilter.EM_REQUEST_ATTRIBUTE);

        ctx.setEntityManager(em);
        ctx.setPersistenceUnitName("default");
        ctx.setContainerManaged(true);
        return ctx;
    }
}
Olingoは、このクラスがすべてのOData要求の処理に使用される新しい_ODataJPAContextを取得する場合、_initializeJPAContext()_メソッドを呼び出します。 ここでは、基本クラスの_getODataJPAContext()_メソッドを使用して「プレーン」インスタンスを取得し、それからカスタマイズを行います。
このプロセスはやや複雑であるため、UMLシーケンスを描画して、これがどのように発生するかを視覚化します。
link:/uploads/Olingo-Request-Processing-100x67.png%20100w []
_setEntityManagerFactory()。の代わりに_setEntityManager()_を意図的に使用していることに注意してください。トランザクションで。
このため、既存の_EntityManager_インスタンスを渡し、そのライフサイクルが外部で管理されていることを通知します。 挿入された_EntityManager_インスタンスは、現在のリクエストで使用可能な属性から取得されます。 この属性の設定方法については、後で説明します。

3.3. ジャージーリソース登録

次のステップでは、_ServiceFactory_をOlingoのランタイムに登録し、OlingoのエントリポイントをJAX-RSランタイムに登録します。 _ResourceConfig_派生クラス内でそれを行います。ここでは、サービスのODataパスを_ / odata_に定義します。
@Component
@ApplicationPath("/odata")
public class JerseyConfig extends ResourceConfig {
    public JerseyConfig(CarsODataJPAServiceFactory serviceFactory, EntityManagerFactory emf) {
        ODataApplication app = new ODataApplication();
        app
          .getClasses()
          .forEach( c -> {
              if ( !ODataRootLocator.class.isAssignableFrom(c)) {
                  register(c);
              }
          });

        register(new CarsRootLocator(serviceFactory));
        register(new EntityManagerFilter(emf));
    }

    // ... other methods omitted
}
  • Olingoが提供する_ODataApplication_は、標準のコールバック* _ * getClasses()*を使用していくつかのプロバイダーを登録する通常のJAX-RS _Application_クラスです。 _

    _ODataRootLocator_クラス以外はすべてそのまま使用できます。 この特定のものは、Javaの_newInstance()_メソッドを使用して__ODataJPAServiceFactory __implementationをインスタンス化します。 ただし、Springで管理してほしいので、カスタムロケーターに置き換える必要があります。
    このロケーターは、Olingoのストック_ODataRootLocator_を拡張する非常にシンプルなJAX-RSリソースであり、必要なときにSpring管理の__ServiceFactory ___を返します。
@Path("/")
public class CarsRootLocator extends ODataRootLocator {
    private CarsODataJPAServiceFactory serviceFactory;
    public CarsRootLocator(CarsODataJPAServiceFactory serviceFactory) {
        this.serviceFactory = serviceFactory;
    }

    @Override
    public ODataServiceFactory getServiceFactory() {
       return this.serviceFactory;
    }
}

3.4. _ EntityManager _フィルター

ODataサービスの最後の残りの__EntityManagerFilter __ ** _._このフィルターは、現在のリクエストに_EntityManager_を挿入するため、_ServiceFactory _ **で使用できます。 これは、__ ContainerRequestFilter __および_ContainerResponseFilter_インターフェースの両方を実装する単純なJAX-RS _ @ Provider_クラスであるため、トランザクションを適切に処理できます。
@Provider
public static class EntityManagerFilter implements ContainerRequestFilter,
  ContainerResponseFilter {

    public static final String EM_REQUEST_ATTRIBUTE =
      EntityManagerFilter.class.getName() + "_ENTITY_MANAGER";
    private final EntityManagerFactory emf;

    @Context
    private HttpServletRequest httpRequest;

    public EntityManagerFilter(EntityManagerFactory emf) {
        this.emf = emf;
    }

    @Override
    public void filter(ContainerRequestContext ctx) throws IOException {
        EntityManager em = this.emf.createEntityManager();
        httpRequest.setAttribute(EM_REQUEST_ATTRIBUTE, em);
        if (!"GET".equalsIgnoreCase(ctx.getMethod())) {
            em.getTransaction().begin();
        }
    }

    @Override
    public void filter(ContainerRequestContext requestContext,
      ContainerResponseContext responseContext) throws IOException {
        EntityManager em = (EntityManager) httpRequest.getAttribute(EM_REQUEST_ATTRIBUTE);
        if (!"GET".equalsIgnoreCase(requestContext.getMethod())) {
            EntityTransaction t = em.getTransaction();
            if (t.isActive() && !t.getRollbackOnly()) {
                t.commit();
            }
        }

        em.close();
    }
}
リソース要求の開始時に呼び出される最初の_filter()_メソッドは、提供された_EntityManagerFactory_を使用して新しい_EntityManager_インスタンスを作成します。 また、副作用がないはずなので、GETリクエストもスキップします。したがって、トランザクションは必要ありません。
2番目の_filter()_メソッドは、Olingoがリクエストの処理を終了した後に呼び出されます。 ここでは、リクエストメソッドもチェックし、必要に応じてトランザクションをコミットします。

3.5. テスト

簡単な_curl_コマンドを使用して実装をテストしましょう。 最初にできることは、サービス_ $ metadata_ドキュメントを取得することです:
curl http://localhost:8080/odata/$metadata
予想どおり、ドキュメントには_CarMaker_と_CarModel_の2つのタイプとassociation __.__の2つのタイプが含まれています。さて、トップレベルのコレクションとエンティティを取得して、サービスをもう少し試してみましょう。
curl http://localhost:8080/odata/CarMakers
curl http://localhost:8080/odata/CarModels
curl http://localhost:8080/odata/CarMakers(1)
curl http://localhost:8080/odata/CarModels(1)
curl http://localhost:8080/odata/CarModels(1)/CarMakerDetails
それでは、名前が「B」で始まるすべての_CarMakers_を返す簡単なクエリをテストしてみましょう。
curl http://localhost:8080/odata/CarMakers?$filter=startswith(Name,'B')
サンプルURLのより完全なリストは、https://www.baeldung.com/odata [OData Protocol Guide article]で入手できます。

5. 結論

この記事では、Olingo V2を使用して、JPAドメインを基にした簡単なODataサービスを作成する方法を説明しました。
この記事の執筆時点では、V4のJPAモジュールの作業を追跡するhttps://issues.apache.org/jira/browse/OLINGO-549[OlingoのJIRAの未解決の問題]がありますが、最後のコメントは2016年に遡ります。 サードパーティのオープンソースJPAアダプターhttps://github.com/SAP/olingo-jpa-processor-v4[SAPのGitHubリポジトリでホスト]もありますが、これはまだリリースされていませんが、この時点ではより機能が充実しているようですオリンゴのものより。
いつものように、この記事のすべてのコードはhttps://github.com/eugenp/tutorials/tree/master/apache-olingo[GitHubで]から入手できます。