1. 序章

このチュートリアルは、 ODataプロトコルガイドのフォローアップであり、ODataプロトコルの基本について説明しました。

次に、ApacheOlingoライブラリを使用して単純なODataサービスを実装する方法を説明します。

このライブラリは、ODataプロトコルを使用してデータを公開するためのフレームワークを提供します。これにより、内部データベースにロックされてしまう情報に、標準ベースで簡単にアクセスできるようになります。

2. オリンゴとは?

Olingoは、Java環境で使用できる「機能化された」OData実装の1つであり、もう1つはSDLODataフレームワークです。 これはApacheFoundationによって維持されており、次の3つの主要モジュールで構成されています。

  • Java V2 –ODataV2をサポートするクライアントおよびサーバーライブラリ
  • Java V4 –ODataV4をサポートするサーバーライブラリ
  • Javascript V4 – Javascript、ODataV4をサポートするクライアント専用ライブラリ

この記事では、JPAとの直接統合をサポートするサーバー側のV2Javaライブラリのみを取り上げます。 結果として得られるサービスは、CRUD操作と、順序付け、ページング、フィルタリングなどの他のODataプロトコル機能をサポートします。

一方、Olingo V4は、コンテンツタイプのネゴシエーションやURL解析など、プロトコルの下位レベルの側面のみを処理します。 これは、メタデータの生成、URLパラメータに基づくバックエンドクエリの生成などに関するすべての本質的な詳細をコーディングするのは、開発者の責任であることを意味します。

JavaScriptクライアントライブラリについては、ODataはHTTPベースのプロトコルであるため、任意のRESTライブラリを使用してアクセスできるため、ここでは省略します。

3. OlingoJavaV2サービス

プロトコル自体の簡単な紹介で使用した2つのEntitySetを使用して単純なODataサービスを作成しましょう。 基本的に、Olingo V2は単にJAX-RSリソースのセットであるため、それを使用するために必要なインフラストラクチャを提供する必要があります。 つまり、JAX-RS実装と互換性のあるサーブレットコンテナが必要です。

この例では、 Spring Boot を使用することを選択しました。これは、サービスをホストするのに適した環境をすばやく作成する方法を提供するためです。 また、ODataのEntityDataModelを作成するために必要なすべてのデータを収集するためにユーザー提供のEntityManagerと直接「通信」するOlingoのJPAアダプターを使用します。

厳密な要件ではありませんが、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の中央リポジトリで入手できます。

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

3.1. ドメインクラス

OlingoでJPAベースのODataサービスを実装するための最初のステップは、ドメインエンティティを作成することです。 この簡単な例では、CarMakerCarModelの2つのクラスを、1対多の関係で作成します。

@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;
    }
}

このクラスがすべてのOData要求の処理に使用される新しいODataJPAContextを取得する場合、Olingoは initializeJPAContext()メソッドを呼び出します。 ここでは、基本クラスから getODataJPAContext()メソッドを使用して「プレーン」インスタンスを取得し、それをカスタマイズします。

このプロセスはやや複雑なので、UMLシーケンスを描画して、これがどのように行われるかを視覚化してみましょう。

意図的に使用していることに注意してください setEntityManager() それ以外の setEntityManagerFactory()。 Springから取得することもできますが、Olingoに渡すと、Spring Bootがライフサイクルを処理する方法(特にトランザクションを処理する場合)と競合します。

このため、既存の 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は、標準のコールバックを使用していくつかのプロバイダーを登録する通常のJAX-RS Applicationクラスです。 getClasses()。 

ODataRootLocatorクラス以外はそのまま使用できます。 この特定のものは、Javaの newInstance()メソッドを使用してODataJPAServiceFactory実装をインスタンス化する責任があります。 ただし、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インスタンスを作成します。後でServiceFactoryによって回復できるようにする属性。 また、副作用がないため、GETリクエストをスキップします。したがって、トランザクションは必要ありません。

2番目のfilter()メソッドは、Olingoが要求の処理を終了した後に呼び出されます。 ここでは、リクエストメソッドも確認し、必要に応じてトランザクションをコミットします。

3.5. テスト

単純なcurlコマンドを使用して実装をテストしてみましょう。 これが最初にできることは、サービス $metadataドキュメントを取得することです。

curl http://localhost:8080/odata/$metadata

予想どおり、ドキュメントにはCarMakerCarModelの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のより完全なリストは、ODataプロトコルガイドの記事にあります。

5. 結論

この記事では、Olingo V2を使用して、JPAドメインに基づく単純なODataサービスを作成する方法を説明しました。

この記事の執筆時点で、V4用のJPAモジュールの作業を追跡するOlingoのJIRA 未解決の問題がありますが、最後のコメントは2016年にさかのぼります。 SAPのGitHubリポジトリでホストされているサードパーティのオープンソースJPAアダプターもあります。これはリリースされていませんが、現時点ではOlingoのものよりも機能が充実しているようです。

いつものように、この記事のすべてのコードは、GitHubから入手できます。