2023年1月20日金曜日

Microsoft OneDriveを操作するAPEXアプリの作成(7) - ODataプラグイン

 せっかくMicrosoft Azure ADによるOIDC認証の構成を行ったので、RESTデータ・ソースを簡易HTTPからODataプラグインに置き換えてみました。

Oracle APEXのODataプラグインについては、以下のAPEX Office Hourで紹介されています。

APEX and OData? REST Data Sources at your service!
https://asktom.oracle.com/pls/apex/asktom.search?oh=19326


ODataプラグインのインストール


ODataプラグインは、GitHubの以下のリンクから取得できます。

odata_connector_sample_app.zipをダウンロードし、APEXのワークスペースに一般的なAPEXアプリケーションとしてインポートします。


ODataプラグインは以下のパッケージに実装されています。

PLG_ODATA_CONNECTOR
PLG_ODATA_FILTERS
PLG_ODATA_METADATA
PLG_ODATA_ORDER_BYS

これらのパッケージの作成は、サンプル・アプリケーションのインストール・スクリプトに含まれています。そのため、サンプル・アプリケーションをインポートする際にサポートするオブジェクトのインストールは必ずONにします。


ODataプラグインの実装であるweb_source_type_plugiin_odata-connector.sqlを、プラグインとしてインポートするだけでは上記のパッケージが作成されません。そのため、プラグインのインポートだけではODataプラグインは動作しません。

サンプル・アプリケーションのインポートが、ODataプラグインをインストールする最も簡単な方法です。


ODataプラグインの作成



共有コンポーネントプラグインを開きます。


作成済みのプラグインが一覧されます。作成をクリックします。


プラグインの作成として既存のプラグインのコピーを選択します。

へ進みます。


アプリケーションからコピーとしてSample ODATA-Connectorを選択します。

へ進みます。


コピー対象として、タイプRESTデータ・ソース名前OData REST Serviceというプラグインが表示されます。

コピーしますか?として、はいまたはコピーおよびサブスクライブのどちらかを選択します。

プラグインのコピーをクリックします。


ODataプラグインが作成されました。


OData REST ServiceプラグインによるRESTデータ・ソースが作成できるようになりました。


RESTデータ・ソースの作成



今までの記事で作成したRESTデータ・ソース、My Drives、My Root、My Folderと同じRESTデータ・ソースを、OData REST Serviceプラグインを使用して作成します。

作成済みのRESTデータ・ソースのエンドポイントURLをメモしておきます。OData REST Serviceプラグインでは、簡易HTTPでのエンドポイントURLを、エンドポイントURLResource Pathに分割して登録します。


はじめにMy Drivesと同じRESTデータ・ソースを作成します。

RESTデータ・ソース・タイプとしてOData REST Serivce[プラグイン]を選択します。名前OData My Drivesとします。URLエンドポイントとして、以下を指定します。

https://graph.microsoft.com/v1.0/me/

へ進みます。


リモート・サーバーおよびサービスURLパスは自動的に決まります。

変更せずにへ進みます。


OData REST Serviceプラグインとしての設定になります。

Resource Pathdrivesとします。エンドポイントURL + Resource Pathが実際にリクエストされるGraph APIのURLです。

へ進みます。


認証を設定します。

これ以降は、タイプが簡易HTTPで作成したRESTデータ・ソースMy Drivesと同じ作業を行います。サンプル・レスポンスdrives.jsonを使用してデータ・プロファイルを更新します。


OData REST SerivceプラグインによるRESTデータ・ソースでは、ODataとしてサポートされている機能を選択する必要があります。サポートされていない機能が有効になっていると、REST APIの発行時にエラーが発生します。

Microsoftのドキュメントより、サポートされている機能を確認します。

Microsoft Graphでのファイルの作業

ドライブを一覧表示するでは、以下のように$expand$select$skipToken$top$orderbyがサポートされているとのことです。


設定項目のオンライン・ヘルプに、対応するODataのクエリパラメーターの説明が記述されています。

このRESTデータ・ソースでは、Basic OptionsSource supports orderingSource supports querying of specific columnsにチェックを入れます。


Source supports client-driven paginationですが、ページネーションはOData REST Serivceプラグインではクエリパラメーターの$top$skipを使って実施します。Microsoftの以下のドキュメントによると、$skipは問題があるため$skipTokenを代わりに使うとのことです。

Use $skiptoken for server-driven paging
https://learn.microsoft.com/en-us/odata/webapi/skiptoken-for-server-side-paging

OData REST Serviceプラグインは$skipTokenを使用したページネーションをサポートしていないため、Graph APIが$skipではなく$skipTokenを実装している場合は、Source supports clilent-driven paginationにチェックを入れることはできません。

続いてMy Rootと同じRESTデータ・ソースOData My Rootを作成します。

Resource Pathdrive/root/childrenになります。その他は、My Rootの作成と同じ手順になります。


ドライブの項目子を一覧表示するオプションのクエリパラメーターは以下です。


先ほどと同じなので、Basic OptionsSource supports orderingSource supports querying of specific columnsにチェックを入れます。


最後にMy Folderと同じRESTデータ・ソースOData My Folderを作成します。

Resource Pathとしてdrive/items/:item_id/childrenを指定します。Resource Pathに含まれるパラメータはURLエンドポイントとは異なり、パラメータとして自動的に検出されません。そのため、RESTデータ・ソース作成後に手作業でパラメータitem_idを追加します。

これ以降の作業はRESTデータ・ソースMy Folderと同じです。


RESTデータ・ソースOData My Folderを作成し、Basic Optionsの設定を行った後に、パラメータの追加を行います。


パラメータの追加をクリックします。


パラメータタイプとしてURLパターン変数を選択します。名前item_idデフォルト値になります。フォルダのDriveItemのIDを入力しておきます。空でも問題ありません。

目的入力詳細必須ONにします。

以上で、パラメータの追加をクリックします。


パラメータが追加されました。変更の適用をクリックします。


以上で、RESTデータ・ソースOData My DrivesOData My RootOData My Folderの作成は完了です。




対話モード・レポートの更新



作成済みの対話モード・レポートのデータ・ソースを入れ替えます。

レポートMy Drivesについては、ソースRESTソースをMy DrivesからOData My Drivesに変更するだけです。


レポートMy Rootでは、ソースの変更前にローカル後処理SQL問合せコピーを取得しておきます。


ソースRESTソースOData My Rootに変更します。ローカル後処理が初期化されるため、先ほどコピーした値で設定を回復します。


レポートFolderRESTソースOData My Folderに変更します。ローカル後処理SQL問合せは元の設定をコピーし、ソース変更後に回復させます。


レポートFolderの場合、パラメータの設定もリセットされます。

パラメータitem_idを選択し、タイプアイテムアイテムP3_ITEM_IDに設定し直します。


以上で対話モード・レポートの更新は完了です。


ODataプラグインのデバッグ



以上の設定でアプリケーションを実行すると、レポートの表示でエラーが発生しました。


最初にデバッグ出力を有効にして、エラー・メッセージを確認します。


エラー・メッセージは以下です。\"@microsoft, という部分が悪いことはわかりますが、これだけだとデバッグは難しいです。

Parsing OData Select and Expand failed: Syntax error: character '\"' is not valid at position 128 in 'eTag,file,size,lastModifiedBy,createdDateTime,folder,webUrl,fileSystemInfo,cTag,shared,reactions,name,createdBy,parentReference,\"@microsoft,specialFolder,lastModifiedDateTime,id

APEXから発行されたREST APIへのリクエストは、ビューAPEX_WEBSERVICE_LOGから確認できます。

アプリケーション・ビルダーワークスペース・ユーティリティから、Oracle APEXビューを開きます。


ビューの一覧よりAPEX_WEBSERVICE_LOGを探して、開きます。


としてURLHTTP_METHODSTATUS_CODEREQUEST_DATEを含めて、結果をクリックします。


フィルタを設定するなどして行を絞り込み、エラーが発生したURLを特定します。

特定したURLをコピーしておきます。


Graph APIを直接発行する画面より、コピーしたURLを呼び出します。同じエラーが発生します。


"@microsoft,の部分の除き、APIを発行すると成功します。


以上より、ODataのクエリパラメータ$selectに渡されている引数に問題があることがわかります。

RESTデータ・ソースのデータ・プロファイルより、@microsoftで始まるセレクタを確認します。

DOWNLOADURLが見つかりました。


DOWNLOADURLのセレクタは"@microsoft.graph.downloadUrl"ですが、このような形式のセレクタをOData REST Sourceプラグインは正しく扱えないようです。


列DOWNLOADURLはアプリケーションで使用しているため、削除することはできません。

設定Source supports querying of specific columnsのチェックを外すことで対応します。


RESTデータ・ソースOData My RootOData My Folderともに、この設定を外すことにより、ODataプラグインによるRESTデータ・ソースに変更したアプリケーションが動作するようになります。

$skipTokenやセレクタなど、OData REST ServiceプラグインのMicrosoft Graph APIとの互換性については残念な部分もありますが(オープンソースなので自分で修正することもできます)、OData自体は将来性があると思います。

変更後のアプリケーションのエクスポートを以下に置きました。
https://github.com/ujnak/apexapps/blob/master/exports/onedrive-operation-odata.zip

Oracle APEXのアプリケーション作成の参考になれば幸いです。

追記

パッケージplg_odata_connector.pkbに以下の記述があり、この部分(regexp_replace)がOneDriveのGraph APIと互換性がないようです。セレクタにはピリオドが含まれないという前提になっています。Graph APIは属性valueにJSONオブジェクトの配列が返しますが、一行にあたるJSONオブジェクトはスカラー値のみを含みJSONオブジェクトは含まない、と想定されているようです。
        --
        -- Cleanup the requested attribute before selection
        --
        l_requested_column_list := regexp_replace( l_requested_column_list, '\.[^\,]*' );

        select distinct
            regexp_replace( column_value, '\.[^\,]*' )
        bulk collect into
            l_arr_requested_attribute
        from
            apex_string.split( l_requested_column_list, ',' );

        l_requested_column_list := apex_string.join( l_arr_requested_attribute, ',' );

さて、どうしたものかとは思いますが、オープンソースだと自分で調べることはできます。