2022年5月25日水曜日

Oracle APEXのアプリでHighchartsを使う

 apex.worldを主催しているJuergen Schusterさんが、Oracle APEXで作ったアプリケーションにHighchartsで作ったチャートを組み込む方法を、YouTubeで紹介していました。

紹介された手順に従って、APEXアプリを作ってみました。以下のようなアプリです。


紹介動画はこちらです。

サンプル・アプリケーションで使用するコードは、YouTubeにHow-Toとして書かれているリンク先に書かれています。

以下、Highchartsのサンプル・アプリケーションを作ってみた作業の紹介です。アプリケーションはAlways FreeのAutonomous Transaction Processing、APEX 21.2.6を使用しています。


表EBA_DEMO_TREE_EMPの準備


Highchartsのサンプルは、チャートを表示する元データとして表EBA_DEMO_TREE_EMPを使用しています。この表はOracle APEXのサンプル・アプリケーションのSample Treesに含まれています。そのため、最初にSample Treesのサンプルをインストールします。

APEX 22.1では、ギャラリーから直接GitHub上にあるサンプルをインストールできるようになりましたが、21.2ではできません。そのため、GitHubからサンプル・アプリケーションをダウンロードします。

サンプル・アプリケーションのサイトを開きます。

https://oracle.github.io/apex

APEX Releaseにインストール先となるAPEXのバージョンを選択(今回の例では21.2)し、Sample AppsよりSample Treesを探します。


Sample Treesを見つけたら、Download Appをクリックします。sample-trees.zipが手元にダウンロードされます。

APEXのアプリケーション・ビルダーより、インポートを実行します。

ドラッグ・アンド・ドロップの項目として、先ほどダウンロードしたサンプル・アプリケーションのアーカイブsample-trees.zipを選択します。

へ進みます。

ファイルのインポートの確認画面に移ります。特にすることはないので、そのままへ進みます。


インポートされるアプリケーション自体は使用せず、サポート・オブジェクトとして同時に作成される表のみ作成します。アプリケーション作成にこだわりは無いのでデフォルトのまま、アプリケーションのインストールを実行します。


サポートするオブジェクトのインストールは、必ずONにします。へ進みます。


確認画面が表示されます。インストールを実行します。


アプリケーションのインストールが完了します。


SQLワークショップを開くと、最近作成された表としてEBA_DEMO_TREE_EMPがリストされています。


以上でデータの準備は完了です。


APEXアプリケーションの作成



空のアプリケーションを作成し、そのアプリケーションにHighchartsを組み込みます。

アプリケーション作成ウィザードを起動します。アプリケーションの名前はHighchartsとし、アプリケーションの作成を実行します。


アプリケーションが作成されます。今回のサンプルはホーム・ページに実装します。




ホーム・ページへのHighchartsの組み込み


ページ・プロパティJavaScriptファイルURLに、How ToページPage Attributes > JavaScript > File URLsの内容を記述します。ホーム・ページのロード時に、Highchartsが実装されているJavaScriptのファイルが読み込まれるようになります。


チャートを表示するリージョンを作成します。

レンダリング・ツリーのBody以下に新規にリージョンを作成します。名前Highchartsタイプとして静的コンテンツを選択します。

リージョンのソースHTMLコードとして、How ToページStatic Region > Sourceの内容を記述します。divタグのidとしてhighcarts-containerが指定されています。このIDを指定して、Highchartsによるチャートの作成を行います。


左ペインプロセス・ビューを開きます。

Ajaxコールバックとして、チャートで表示するデータをJSON形式で返すプロセスget_dataを作成します。プロセスの識別名前get_dataとします。

ソースPL/SQLコードとして、How ToページAjax Callback > get_dataの内容を記述します。表EBA_DEMO_TREE_EMPの検索結果をファンクションJSON_OBJECTを使ってJSONフォーマットにし、ファンクションHTP.Pによって呼び出し元へ返却しています。


Ajaxコールバックを呼び出して受け取ったデータを元に、Highchartsを呼び出してチャートを生成するコードを記述します。

左ペインでレンダリング・ビューを表示し、ページ・プロパティのJavaScriptファンクションおよびグローバル変数の宣言に、How ToページPage Attributes > Function and Global Variable Declarationの内容を記述します。

APEXが提供しているJavaScript APIのapex.server.processを使ってAjaxコールバックget_dataを呼び出しています。結果はlResponseに保存します。

lResponseへのデータの取り込みが完了した時点で、Highcharts.chartを呼び出してチャートを生成します。引数はDIVタグのIDであるhighcharts-containerと、生成するチャートの属性を含んだJSONオブジェクト(その中にデータも含まれる)を渡しています。


以上で、サンプル・アプリケーションは完成です。

アプリケーションを実行すると、記事の最小のスクリーンショットにあるようなチャートが表示されます。

Highchartsはそこそこのお値段がしますが、チャートとしての品質は高く、値段に見合った価値があるようです。Juergen SchusterさんはOracle JETは好みではないようで、何年もずっとHighchartsを使用しているとのことです。

コピペしたコードを埋め込んだアプリを公開するのも問題なので、アプリケーションのエクスポートはありません。

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

2022年5月24日火曜日

主キーを自動採番にしたときに、ORA-0001一意制約違反が発生する

 Oracle APEXのクイックSQLを使って表を作成すると、特別な設定をしない限り列IDが表に追加され、自動採番(generated by default on null as identityの設定)になります。

このように構成された表に以下の操作を行うと、ORA-0001 一意制約違反が発生することがあります。

  1. CSVなど、データ・ワークショップを使ってデータをインポートしたとき。
  2. クイックSQLの表ディレクティブ/insertを指定して、サンプル・データを生成したとき。
このような操作が実行された後に、以下のALTER文を実行すると主キーとなっている列の最大値まで進めることができます。
alter table 表名 modify 主キー列名 generated by default on null as identity (start with limit value);
以下より、どのような状況か説明します。

クイックSQLの以下のモデルを使って、表SAMPLE_NOTESを作成します。
sample_notes
    note
以下のDDLが生成されます。モデルでは定義していませんが、主キー列としてIDが追加され、自動採番の設定になっています。
-- create tables
create table sample_notes (
    id                             number generated by default on null as identity 
                                   constraint sample_notes_id_pk primary key,
    note                           varchar2(4000 char)
)
;

生成した表SAMPLE_NOTESを元にアプリケーションの生成までを実施します。作成するアプリケーションの名前一意制約違反とし、対話モード・レポートとフォームのページを含みます。


アプリケーションといっても、表SAMPLE_NOTESにフォームを使ってデータを投入できれば良いだけなので、以上で十分です。

表SAMPLE_NOTESは作成したばかりでデータが投入されたことはないため、シーケンスは1から始まり、列IDには1から連番が振られます。

ここで、データ・ワークショップを使って、以下のCSVを表SAMPLE_NOTESにロードします。
ID, NOTE
5, A
6, B
7, C
SQLワークショップユーティリティから、データ・ワークショップを開き、データのロードを実行します。


コピー・アンド・ペーストを選択し、ロードするデータをウィンドウに貼り付けます。

へ進みます。


ロード先既存の表で、としてSAMPLE_NOTESを選択します。

ここで、列IDをそのままロードしますが、列IDが自動採番ということは、値自体は意味を持ちません(一般にサロゲート・キーと呼びます)。本来、列IDはロード対象から外すべきであり、このデータをロードしなければ一意制約違反は発生しません。


構成をクリックして、ソース列ID未マップ列とすることで、ロードの対象から外すことができます。


IDも含めてデータを表SAMPLE_NOTESにロードしていると、フォームからデータを投入する際にORA-0001 - 一意制約違反が発生することがあります。


シーケンスは1から始まります。データ・ワークショップのデータ・ロードでは、ロード対象のデータが3行だったので、3つシーケンスを進めています。ロードしたデータのIDは

5、6、7

で、フォームから入力する新規データに割り振られるIDはから始まります。(ロードする列IDのデータが1から連番になっている場合は、一意制約違反は発生しません。)

2回目のデータ入力で、IDはとなります。すでにIDが5のデータは存在するため、ORA-0001が発生します。

この状況に対応するため、以下のALTER文を実行します。
alter table sample_notes modify id generated by default on null as identity (start with limit value);

クイックSQLで表ディレクティブ/insert句を使ってサンプル・データを作成した場合も、同様の対応が必要になります。

以下のように/insert 3をつけて、表SAMPLE_NOTESを作成します。
sample_notes /insert 3
    note

サンプルとなるデータを挿入するINSERT文として、以下の文が生成されています。

insert into sample_notes (
    id,
    note
) values (
    1,
    'Suscipit tristique ac volutpat risus.Phasellus vitae ligula commodo, dictum lorem sit amet, imperdiet ex. Etiam cursus porttitor tincidunt.'
);

自動採番は、列IDに値が指定されていないときに実施されます。このINSERT文では列IDにデータが指定されているため、自動採番は行われません。シーケンスの値も進まないため、新規に行を挿入する際に列IDの値として1が割り当てられます。結果として、フォームからデータを挿入する際にORA-0001の一意制約違反が発生します。

この場合も、データのロードと同様にALTER文を実行することで対応できます。

2022年5月20日金曜日

APEXのJavaScriptライブラリに修正を加える

 日付ピッカーで閏日の入力ができないという不具合がありました。APEX 21.2のKnown Issuesにリストされていて(34151906)、APEX 21.2.7のPSEに修正が含まれています。

https://www.oracle.com/tools/downloads/apex-downloads/apex-212-known-issues.html

オンプレであればPSEを適用するのが対策になりますが、Autonomous Databaseではオラクルがパッチを当てるまで待つ必要があります。また、無料でAPEXを利用している場合はPSEを入手できません。

閏日の入力ができないというのは、影響も大きいので少し対策を考えてみました。だだし、サポートされない対処方法です

コード上の修正点は軽微なもので、以下で参照できるdate.jsに含まれるファンクションapex.date.parseに不具合があります。

https://ADBのID-インスタンス名.adb.リージョン名.oraclecloudapps.com/i/21.2.4/libraries/apex/date.js

ここで、2022年を引数に与えてlocalDateの初期化をしていますが、2022年は閏年ではないので、2月29日がありません。これを2020年に変更する必要があります。

let localDate = new Date( 2022, 0, 1 ),

の部分を2020に変更します。

let localDate = new Date( 2020, 0, 1 ),

ファンクションapex.date.parseが含まれるファイルdate.jsは、ソースコードの参照ができるようにAPEXのリリースやパッチに含まれていますが、アプリケーションがロードするのは、この他のJavaScriptのファイルをまとめてミニファイしたdesktop_all.min.jsになります。

このファイルをダウンロードします。

不具合のある日付ピッカーを含んだページを表示します。


ブラウザの機能を使って、表示されているページのソースを表示します。ソース中のdesktop_all.min.jsへのリンクをクリックし、ファイルを手元にダウンロードします。


ダウンロードしたファイルをエディタで開き、2022の部分を2020に変更します。


変更したファイルを、共有コンポーネント静的アプリケーション・ファイルにアップロードします。


静的アプリケーション・ファイルの一覧が表示されます。ファイルの作成をクリックします。


修正したdesktop_all.min.jsを選択し、作成をクリックします。


ファイルがアップロードされます。参照として表示されているパスをクリップボードにコピーします。変更の保存を実行します。一覧に戻るには取消をクリックします。


静的アプリケーション・ファイルとしてdesktop_all.min.jsが保存されていることを確認します。


日付ピッカーを含むページをページ・デザイナで開き、ページ・プロパティのJavaScriptファイルURLとして、コピーした参照を設定します。


アプリケーションを実行し、動作を確認します。少なくとも、日付ピッカーについては閏日の入力ができるようになります。


日付ピッカー以外の要素に与える影響が不明なため、恒久的な修正とはいえません。適用する範囲は最低限のページに限定するのが良いでしょう。サポートされない方法ですが、他に対処方法がなく正式な修正も待てない場合には役に立つのではないかと思います。

オンプレで無料の範囲での利用であれば、もともと製品サポートはありません。ファイル・システム上にあるdesktop_all.min.jsを直接編集しても、サポートが受けられないことに違いはありません。この場合は、ファイルを2重にロードすることにはならないので、修正による日付ピッカー以外への影響は無いでしょう。

2022年5月19日木曜日

APEX_MAIL.PREPARE_TEMPLATEを呼び出し、送信するメールをプレビューする

 メール送信前に、電子メール・テンプレートの穴埋めを行なった結果を画面に表示して確認できないか、との相談がありました。調べてみたところ、プロシージャAPEX_MAIL.PREPARE_TEMPLATEを使うと、送信するメールのプレビューができそうです。

試しに実装してみました。


アプリケーションの作成は、Always FreeのAutonomous Database上のAPEX 21.2を使って行います。また、ソースとなるデータとして、サンプル・データセットEMP/DEPTに含まれる表EMPを使用します。

以下より、サンプルの実装を紹介します。

アプリケーション作成ウィザードを起動します。名前メール・プレビューとします。ホーム・ページは、編集をクリックして設定画面を開き、削除します。


ページの追加を実行し、ファセット検索を選択します。


ページ名従業員検索とします。レポートの表示形式はレポートとしてEMPを選択します。フォームを含めるチェックを入れ、表EMPの編集ページも作成します。この編集ページを、電子メールのプレビューのページに作り直します

ページの追加をクリックします。


アプリケーションの作成を実行します。


アプリケーションが作成されます。

フォームのページEmployeesを開き、メールのプレビューに作り直します。


リージョンEmployeesボタン削除します。これらのリージョンに配置されているページ・アイテムやボタンも削除します。


すべてのリージョンが無くなったところで、Content Bodyに新たにリージョンを作成します。

新規に作成したリージョンの識別名前プレビューとし、タイプPL/SQL動的コンテンツを選択します。ソースPL/SQLコードとして、以下を記述します。コード中で使っている電子メール・テンプレートORDERMAILは、この後に作成します。


プレビューに影響を与える装飾をできるだけ無くすため、外観テンプレート- 選択 -に変更して、テンプレートを使わないようにします。


リージョンプレビューにページ・アイテムP2_EMPNOを作成します。タイプ非表示とします。このページ・アイテムに指定された従業員番号の従業員の情報を使って、電子メール・テンプレートの穴埋めを行います。

以上で、プレビューを行うページは作成できました。

共有コンポーネント電子メール・テンプレートを開いて、テンプレートORDERMAILを作成します。


電子メール・テンプレートの作成を実行します。


テンプレート名ORDERMAIL(これはもっと読みやすい名前にできます)、静的識別子ORDERMAIL(これはコード中で使用しているので変更不可です)とします。電子メールの件名HTMLフォーマットヘッダー本文フッタープレーン・テキスト・フォーマットコンテンツを以下を参考に埋めていきます。(APIの動作さえ確認できればよかったので、正直、かなり手抜きです)。

テンプレートが決まったら、電子メール・テンプレートの作成を実行します。


電子メール・テンプレートORDERMAILが作成されます。


以上でアプリケーションは完成です。アプリケーションを実行すると、本記事の先頭にあるGIF動画のような動作をします。

今回のサンプルで表示しているのは、HTML本文のみです。件名プレーン・テキストはプレビューに含めていません。それでも、確認した範囲ではAPEX_MAIL.PREPARE_TEMPLATEより得られたHTML本文は、十分、プレビューの役に立ちそうです。

今回作成したアプリケーションのエクスポートを以下に置きました。
https://github.com/ujnak/apexapps/blob/master/exports/email-preview.sql

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

APIキーのクリデンシャルにPKCS#1とPKCS#8の両方が使えることを確認する

Oracle CloudでAPIキーを登録する方法として、大きく2つあります。

  1. OCIコンソールでAPIキー・ペアを生成し、秘密キーのダウンロードを実行する。
  2. opensslを実行して、手元でキー・ペアを生成し、公開キーをAPIキーとして登録する。
APIキーの追加ユーザーの詳細リソースより、APIキーを開いて実施します。


APIキーの追加をクリックしてダイアログを開き、APIキー・ペアの生成を選択します。秘密キーのダウンロードをクリックすると、クリデンシャルの作成時に指定する秘密キーが、ファイルとしてダウンロードされます。


ダウンロードされた秘密キーを見ると、ヘッダーとフッターがBEGIN PRIVATE KEYEND PRIVATE KEYであることが確認できます。このファイル・フォーマットはPKCS#8です。
-----BEGIN PRIVATE KEY-----
MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCUH6zriPt/Oa/1
...
[中略]
....
645WM5ZXhQIEeNU7WFhe9Q==
-----END PRIVATE KEY-----
マニュアルでは、以下のコマンドを実行してAPIキーに使うキー・ペアを生成しています。
openssl genrsa -out ~/.oci/oci_api_key.pem 2048
生成されたファイルを見ると、ヘッダーとフッターがBEGIN RSA PRIVATE KEYEND RSA PRIVATE KEYであることが確認できます。このファイル・フォーマットはPKCS#1です。
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAt2juWKiGzd43+Fc3qRfZE4rmgVfR4fmyvlz1Ep+JqY8wUWzU
...
[中略]
...
ui1D93DenZRMJe5SQO4YzWHZobSr0DCR7cqTcsWmwNdZEOK2I725bA==
-----END RSA PRIVATE KEY-----
どちらの方法で作成した秘密キーでも、問題なくクリデンシャルは作れていました。

これらの秘密キーの情報を使って作成するクリデンシャルとして、DBMS_CLOUD.CREATE_CREDENTIALを使って作成するものと、Oracle APEXのWeb資格証明として作成するものがあります。

結論をからいうと、秘密キーとしてPKCS#1とPKCS#8のどちらのフォーマットでクリデンシャル/Web資格証明を作っても問題ありません。

確認のために、オブジェクト・ストレージにJSONファイルを配置し、それを読み取ってみました。JSONファイルは以下のパスよりアクセスします。

https://objectstorage.us-ashburn-1.oraclecloud.com/n/ネームスペース/b/data/o/f1-races-2022.json

クリデンシャルを与えずにアクセスすると、BucketNotFoundが発生します。

{"code": "BucketNotFound","message": "Either the bucket named 'data' does not exist in the namespace 'ネームスペース' or you are not authorized to access it"}


APEXでのWeb資格証明の作成



APEXでは、ワークスペース・ユーティリティWeb資格証明(または各アプリケーションの共有コンポーネントWeb資格証明)よりクリデンシャルを作成します。

Web資格証明名前MY_DATA_CRED8とし、OCI秘密キーにファイル・フォーマットがPKCS#8のデータを与えます。OCIユーザーIDOCIテナンシIDおよびOCI公開キー・フィンガープリントは、APIキーを作成した際に表示される構成情報に含まれています。


ここで使用したPKCS#8の秘密キーをPKCS#1にフォーマット変換します。PKCS#8のデータをpkcs8.pemに書き込み、opensslの以下のコマンドを実行します。
openssl rsa -in pkcs8.pem -outform PEM -out pkcs1.pem
生成したPKCS#1の秘密キーを使って、PKCS#8のときと同様にWeb資格証明を作成します。名前はMY_DATA_CRED1とします。OCI秘密キー以外は同じ値を設定します。


PKCS#1からPKCS#8を生成する場合は、以下のコマンドを使用します。
openssl pkcs8 -topk8 -nocrypt -in pkcs1.pem -outform PEM -out pkcs8.pem


RESTデータ・ソースの確認



APEXアプリケーションの共有コンポーネントより、RESTデータ・ソースを作成します。


作成済のRESTデータ・ソースが一覧されます。作成をクリックします。


RESTデータ・ソースの作成として最初からを選択し、へ進みます。


RESTデータ・ソース・タイプとして簡易HTTPを選択し、名前F1レース-PKCS#8URLエンドポイントとして、オブジェクト・ストレージ上のJSONファイルへのパスを指定します。

へ進みます。


リモート・サーバーベースURLおよびサービスURLパスは自動的に決まるので、そのまま変更しません。

へ進みます。


ページ区切りタイプとして、ページ区切りなしを選択します。

へ進みます。


認証の設定に移ります。認証が必要ですONに変更し、資格証明として作成済のMY_DATA_CRED8(PKCS#8の秘密キーで作成)を選択します。

検出を実行します。


JSONファイルの内容が表示されます。これで資格証明MY_DATA_CRED8が有効であることが確認できました。

RESTデータ・ソースの作成をクリックして、確認作業は終了です。


同様の手順で、資格証明としてMY_DATA_CRED1を使ったRESTデータ・ソースF1レース-PKCS#1を作成します。

こちらも問題なく作成できます。



APEX_WEB_SERVICE.MAKE_REST_REQUESTの確認



APEX_WEB_SERVICE.MAKE_REST_REQUESTを呼び出す以下のコードを実行し、JSONファイルが取得できるか確認します。p_credential_static_idにWeb資格証明の静的IDを指定します。


SQLワークショップSQLコマンドから実行します。


資格証明のMY_DATA_PKCS8MY_DATA_PKCS1の両方で、JSONファイルが取得できることが確認できます。


OCIのクリデンシャルの作成



DBMS_CLOUD.CREATE_CREDENTIALを呼び出す以下のスクリプトを実行し、Oracle CloudのAPIで使用できるクリデンシャルを作成します。引数のuser_ocidtenancy_ocidfingerprintに与える値は、はAPIキーを作成した際に表示された構成情報に含まれています。


秘密キーがPKCS#8のクリデンシャルMY_DATA_CRED8を作成します。APEXのワークスペース・スキーマに、パッケージDBMS_CLOUDの実行権限が割り当てられている必要があります。


同様の手順で、秘密キーがPKCS#1のクリデンシャルMY_DATA_CRED1を作成します。

作成されたクリデンシャルを確認します。
select credential_name, enabled from user_credentials where credential_name like 'MY_DATA%'


APEXのWeb資格証明とは保存先が異なるため、名前が同じでも実体は異なります。


DBMS_CLOUD.SEND_REQUESTの確認



DBMS_CLOUD.SEND_REQUESTを使って確認します。以下のコードを実行します。APEXのワークスペース・スキーマには、パッケージDBMS_CLOUDに加えて、DBMS_CLOUD_TYPESの実行権限が必要です。




クリデンシャルMY_DATA_CRED8およびMY_DATA_CRED1の両方で、JSONファイルの取得に成功します。


PL/SQL SDKの確認



OCIのオブジェクト・ストレージPL/SQL SDKを使用します。APEXのワークスペース・スキーマに以下の権限が与えらられているのが前提です。
grant execute on dbms_cloud_oci_obs_object_storage_get_object_response_t to apexdev;
grant execute on dbms_cloud_oci_obs_object_storage to apexdev;
確認のために、以下のコードを実行します。




クリデンシャルMY_DATA_CRED8およびMY_DATA_CRED1の両方で、JSONファイルの取得に成功します。

以上で、クリデンシャルの秘密キーのフォーマットが、PKCS#1とPKCS#8のどちらでも問題なく使用できることが確認できました。