Oracle® Fusion Middleware Oracle Application Development FrameworkによるFusion Webアプリケーションの開発 12c (12.1.2) E48099-02 |
|
前 |
次 |
この章では、ADFエンティティ・オブジェクトのイベントと機能を使用して、ビジネス・ルールをOracle ADFアプリケーション内でプログラム的に実装する方法について説明します。また、setterメソッドを使用したエンティティ行への移入など、カスタム検証コードの起動方法についても説明します。
この章には次の項が含まれます:
内蔵された宣言型検証機能を補完するため、エンティティ・オブジェクトとビュー・オブジェクトにはJavaコードを使用してカプセル化されたビジネス・ロジックをプログラミングによって実装できる操作可能なメソッド・バリデータといくつかのイベントがあります。これらの概念を、図12-1に示します。
属性レベルのメソッド・バリデータは、属性値が変更されると、検証コードをトリガーします。
エンティティ・レベルのメソッド・バリデータは、エンティティ行が検証されると、検証コードをトリガーします。
あるエンティティに対して、次の主要メソッドをカスタムJavaクラスでオーバーライドできます。
create()
: 行の作成時にデフォルト値を割り当てます。
initDefaultExpressionAttributes()
: 行の作成時、または新規行のリフレッシュ時にデフォルト値を割り当てます。
remove()
: 条件付きで削除を制限します。
isAttributeUpdateable()
: 属性を条件付きで更新可能にします。
setAttribute()
: 属性レベルのメソッド・バリデータをトリガーします。
validateEntity()
: エンティティ・レベルのメソッド・バリデータをトリガーします。
prepareForDML()
: エンティティ行が保存される前に属性値を割り当てます。
beforeCommit()
: 指定された型のすべてのエンティティ行に関連する規則を実行します。
afterCommit()
: エンティティ・オブジェクトの状態の変更に関する通知を送信します。
注意: 新規行と変更された行のみがトランザクション検証の対象となります。削除された行はトランザクションの一部ですが、検証には含まれません。 プログラム的なビジネス・ルールをコーディングする場合、検証サイクルを正確に把握しておくことが重要です。詳細は、11.2項「検証サイクルの理解」を参照してください。 |
ほとんどの検証は基本的な宣言的操作で実装できますが、メソッド・バリデータを使用してカスタム検証コードを起動することにより、必要に応じてビジネス・ドメイン・レイヤーにより複雑なビジネス・ルールを実装できます。
プログラム的なビジネス・ルールを使用する場合としては、次の例があります。
データベース順序からの属性値のデフォルト設定
複雑な計算から導出された値の割当て
エンティティ・オブジェクトに対する保留中の変更の取消し
現在のユーザー・セッションに関する情報のアクセスと格納
属性の条件付き更新可能性の判断
プログラム的な検証を使用する前に、他のOracle ADF機能を理解しておくと役立つ場合があります。次に、関連する他の機能へのリンクを示します。
ビジネス・ルールをプログラム的に実装する前に、宣言的検証でアプリケーションのニーズに対応できるかどうかを確認する必要があります。詳細は、第11章「検証とビジネス・ルールの宣言的な定義」を参照してください。
リソース・バンドルを使用して、ローカライズ可能な検証エラー・メッセージを用意できます。4.7項「リソース・バンドルの使用」を参照してください。
4.10.9項「トリガーが割り当てられた値との同期方法」で、主キーの属性にDBSequence
型を使用する方法を説明しています。この種の属性の値は、コミット時にデータベース・シーケンスによって取り込む必要があります。
認証されたユーザーに関する情報へのアクセス方法など、Oracle Fusion Webアプリケーションのセキュリティ機能の詳細は、第41章「Fusion WebアプリケーションでのADFセキュリティの有効化」を参照してください。
エンティティ・オブジェクトのビジネス・ロジックでのGroovyスクリプトの使用方法の詳細は、3.5.6項「ビジネス・コンポーネントでのGroovyスクリプト言語の使用方法」を参照してください。
メソッド・バリデータは、独自のJavaコードを使用した宣言的な検証規則とGroovyスクリプト式を補完する主要な手段です。メソッド・バリデータでは、独自の検証メソッド内に記述したJavaコードが、エンティティ・オブジェクト検証サイクルにおいて適切なタイミングでトリガーされます。メソッド・バリデータにより、属性またはエンティティ全体で、様々な型の検証をコーディングできます。
それぞれがコード内の異なるメソッド名をトリガーしていれば、任意の数の属性レベルまたはエンティティレベルのメソッドを追加できます。すべての検証メソッド名はvalidate
というワードで開始する必要があります。ただし、この規則にさえ従っていれば、機能を明確に示す任意の方法で名前を付けることができます。属性レベルのバリデータでは、メソッドはエンティティ属性と同じ型の引数を1つだけ取ります。エンティティレベルのバリデータでは、メソッドは引数を取りません。メソッドはパブリックで、ブール値を返す必要があります。メソッドからfalse
が返されると、検証は失敗します。
注意: これらの規則を知っておくことは必要ですが、JDeveloperを使用してメソッド・バリデータを作成する場合、そのクラスの正しいインタフェースが作成されます。 |
実行時には、メソッド・バリデータにより、エンティティ属性がエンティティ・オブジェクト・クラスで実装されているメソッドに渡されます。
例12-1では、メソッドは大文字で始まり、NULL値で例外をスローする文字列、空の文字列および大文字で始まらない文字列を受け取ります。
例12-1 先頭が大文字の場合に検証を行うメソッド
public boolean validateIsCapped(String text) { if (text != null && text.length() != 0 && text[0] >= 'A' && text[0] <= 'Z') { return true; } }
メソッド・バリデータの使用は、宣言的検証規則をプログラム的に補完する手段です。
始める前に:
メソッド・バリデータに関する知識があると役立つ場合があります。詳細は、12.2項「メソッド・バリデータの使用」を参照してください。
また、他の検証機能を使用して追加できる機能についても理解しておくと役立ちます。詳細は、12.1.2項「プログラム的なビジネス・ルールの追加機能」を参照してください。
属性レベルのメソッド・バリデータを作成するには:
「アプリケーション」ウィンドウで、目的のエンティティ・オブジェクトをダブルクリックします。
概要エディタで、「Java」ナビゲーション・タブをクリックします。
Javaページには、そのエンティティ・オブジェクトに対して現在有効なJava生成オブジェクトが表示されます。エンティティ・オブジェクトにカスタム・エンティティ・オブジェクト・クラスがない場合は、Methodバリデータの追加前に生成しておく必要があります。カスタムJavaクラスを生成するには、「編集」アイコンをクリックし、次に「エンティティ・オブジェクト・クラスの生成」を選択し、「OK」をクリックして、*.java
ファイルを生成します。
「ビジネス・ルール」ナビゲーション・タブをクリックし、「属性」セクションを開いて、検証する属性を選択します。
「新規」アイコンをクリックし、検証規則を追加します。
「検証ルールの追加」ダイアログの「ルール・タイプ」ドロップダウン・リストで「メソッド」を選択します。
「検証ルールの追加」ダイアログに、属性レベルの検証メソッドで期待されるメソッド・シグネチャが表示されます。次のいずれかを選択できます。
エンティティ・オブジェクトのカスタムJavaクラスに、適切なシグネチャのあるメソッドがすでに存在する場合、リストに表示されます。これは「メソッドの作成と選択」チェック・ボックスの選択を解除した後に選択できます。
「メソッドの作成と選択」チェック・ボックスを選択したままにすると(図12-2を参照)、validate
から始まるどのようなメソッド名でも「メソッド名」ボックスに入力できます。「OK」をクリックすると、適切なシグネチャとともに、メソッドがエンティティ・オブジェクトのカスタムJavaクラスに追加されます。
必要に応じて、「検証実行」タブをクリックして、依存属性や事前条件式などのルールの実行基準を入力します。詳細は、11.6項「検証実行のトリガー」を参照してください。
「失敗処理」タブをクリックして、検証規則が失敗した場合にユーザーに対して表示されるエラー・メッセージを入力または選択します。
詳細は、11.7項「検証エラー・メッセージの作成」を参照してください。
メソッド・バリデータを新たに追加すると、JDeveloperでは新しい検証ルールを反映するように、XMLドキュメントが更新されます。メソッドの作成を指定した場合、そのメソッドはエンティティ・オブジェクトのカスタムJavaクラスに追加されます。例12-2は、オーダーのOrderShippedDate
が現在月の日付であることを確認する、簡単な属性レベルの検証規則を示しています。メソッドには、対応する属性と同じ型の引数が与えられ、その条件付きのロジックはこの入力パラメータの値に基づいています。属性バリデータの起動時、属性値は対象となる新しい新しい値にはまだ設定されていません。したがって、getOrderShippedDate()
メソッドをOrderShippedDate
属性に対する属性バリデータ内でコールしても、クライアントが設定しようとしている候補値ではなく、属性の現在の値が返されます。
例12-2 簡単な属性レベルのメソッド・バリデータ
public boolean validateOrderShippedDate(Date data) { if (data != null && data.compareTo(getFirstDayOfCurrentMonth()) <= 0) { return false; } return true; }
注意:
|
エンティティ・レベルのメソッド・バリデータは、有効範囲がより広く、単一の属性ではなくエンティティ全体が範囲になる点を除けば、属性レベルのメソッド・バリデータと似ています。
さらに、エンティティレベルのメソッド・バリデータを使用している場合は、バリデータの実行をトランザクションの時間まで遅延できます。
始める前に:
メソッド・バリデータに関する知識があると役立つ場合があります。詳細は、12.2項「メソッド・バリデータの使用」を参照してください。
また、他の検証機能を使用して追加できる機能についても理解しておくと役立ちます。詳細は、12.1.2項「プログラム的なビジネス・ルールの追加機能」を参照してください。
エンティティ・レベルのメソッド・バリデータを作成するには:
「アプリケーション」ウィンドウで、目的のエンティティ・オブジェクトをダブルクリックします。
概要エディタで、「Java」ナビゲーション・タブをクリックします。
Javaページには、そのエンティティ・オブジェクトに対して現在有効なJava生成オブジェクトが表示されます。エンティティ・オブジェクトにカスタム・エンティティ・オブジェクト・クラスがない場合は、Methodバリデータの追加前に生成しておく必要があります。カスタムJavaクラスを生成するには、「編集」アイコンをクリックし、次に「エンティティ・オブジェクト・クラスの生成」を選択し、「OK」をクリックして、*.java
ファイルを生成します。
「ビジネス・ルール」ナビゲーション・タブをクリックして「エンティティ」ノードを選択し、「新規」アイコンをクリックして検証ルールを追加します。
「検証ルールの追加」ダイアログの「ルール・タイプ」ドロップダウン・リストで「メソッド」を選択します。
「検証ルールの追加」ダイアログに、エンティティレベルの検証メソッドで期待されるメソッド・シグネチャが表示されます。次のいずれかを選択できます。
エンティティ・オブジェクトのカスタムJavaクラスに、適切なシグネチャのあるメソッドがすでに存在する場合、リストに表示されます。これは「メソッドの作成と選択」チェック・ボックスの選択を解除した後に選択できます。
「メソッドの作成と選択」チェック・ボックスを選択したままにすると(図12-3を参照)、validate
から始まるどのようなメソッド名でも「メソッド名」ボックスに入力できます。「OK」をクリックすると、適切なシグネチャとともに、メソッドがエンティティ・オブジェクトのカスタムJavaクラスに追加されます。
必要に応じて、「検証実行」タブをクリックして、依存属性や事前条件式などのルールの実行基準を入力します。詳細は、11.6項「検証実行のトリガー」を参照してください。
Methodエンティティ・バリデータの場合は、「検証実行」タブを使用して検証レベルを指定することもできます。「トランザクション・レベルの遅延実行」を選択した場合は、エンティティがコミットされるとバリデータが起動します。これは、1つの表に編集対象の複数の行が表示される場合に役立ちます。
「失敗処理」タブをクリックして、検証規則が失敗した場合にユーザーに対して表示されるエラー・メッセージを入力または選択します。
詳細は、11.7項「検証エラー・メッセージの作成」を参照してください。
メソッド・バリデータを新たに追加すると、JDeveloperでは新しい検証ルールを反映するように、XMLドキュメントが更新されます。メソッドの作成を指定した場合、そのメソッドはエンティティ・オブジェクトのカスタムJavaクラスに追加されます。例12-3は、注文のDateShipped
がDateOrdered
より後の日付であることを確認する、簡単なエンティティ・レベル検証ルールを示しています。
例12-3 簡単なエンティティ・レベルのメソッド・バリデータ
public boolean validateDateShippedAfterDateOrdered() { Date DateShipped = getDateShipped(); Date DateOrdered = getDateOrdered(); if (DateShipped != null && DateShipped.compareTo(DateOrdered) < 0) { return false; } return true; }
「検証ルールの追加」ダイアログの「検証実行」タブで「トランザクション・レベルの遅延実行」を選択し、エンティティ・オブジェクトを表示するページでSkipValidation="skipDataControls"
と設定した場合は、トランザクションの時間まで検証を延期できます。これにより、1つの表で複数の行を変更してから、ユーザーのコミット時にそれらをグループとして検証できます。例12-4は、エンティティ行の集合の部門名がNULLでないことを検証するメソッド・バリデータを示しています。
例12-4 簡単なトランザクション・レベルのメソッド・バリデータ
// Validation method for Departments. public boolean validateDepartments(ArrayList ctxList) { Iterator iter = ctxList.iterator(); while (iter.hasNext()) { JboValidatorContext entValContext = (JboValidatorContext)iter.next(); DepartmentsImpl deptEO = (DepartmentsImpl)entValContext.getSource(); if (deptEO.getDepartmentName() == null) { // if Dept is null, throw error return false; } } return true; }
エンティティ・オブジェクト属性のロケール固有のUIコントロールのヒントと同様に、検証規則のエラー・メッセージは、エンティティ・オブジェクトのコンポーネント・メッセージ・バンドル・ファイルに追加されます。メッセージ・バンドル内のこれらのエンティティは、アプリケーションのデフォルトのロケールの文字列を表します。検証エラー・メッセージの翻訳バージョンについては、4.7項「リソース・バンドルの使用」で説明したUIコントロール・ヒントの翻訳と同様の手順で追加してください。
宣言的なデフォルト設定では不十分なとき、次の場合にエンティティ・オブジェクトでプログラムによるデフォルト設定を実行できます。
エンティティ行が最初に作成される場合
エンティティ行が最初に作成される場合、またはリフレッシュされてNULL値に設定される場合
エンティティ行がデータベースに保存される場合
エンティティ属性値が設定される場合
create()
メソッドは、エンティティ行の最初の作成時にデフォルト値の初期化処理を可能にするエンティティ・オブジェクト・イベントを提供します。例12-5は、エンティティ・オブジェクトの作成メソッドのオーバーライドを示しています。このメソッドは、新しい注文エンティティ行に、DateOrdered
属性を移入する属性のsetterメソッドをコールします。
デフォルト値は、Groovy式を使用して定義することもできます。詳細は、4.10.6項「静的なデフォルト値を定義する方法」を参照してください。
例12-5 新しい行の属性値に対する、プログラムによるデフォルト設定
// In entity object implementation class protected void create(AttributeList nameValuePair) { super.create(nameValuePair); this.setDateOrdered(new Date()); }
注意: オーバーライドされた |
行が最初に作成されたとき、および初期化状態にリフレッシュされたときの両方で実行するプログラム的なデフォルト設定ロジックでは、initDefaultExpressionAttributes()
メソッドをオーバーライドする必要があります。
エンティティ行がNew
状態であり、そのエンティティ行に対してrefresh()
メソッドをコールした場合、REFRESH_REMOVE_NEW_ROWS
フラグまたはREFRESH_FORGET_NEW_ROWS
フラグを指定しないと、そのエンティティ行はInitialized
状態に戻ります。このプロセスの一環として、エンティティ・オブジェクトのinitDefaultExpressionAttributes()
メソッドが実行されますが、create()
メソッドは再度実行されません。
4.10.9項「トリガーが割り当てられた値との同期方法」で、主キーの属性にDBSequence
型を使用する方法を説明しています。この種の属性の値は、コミット時にデータベース・シーケンスによって取り込む必要があります。エンティティ行の作成時にユーザーが値を参照でき、この値がデータ保存時に変更されないように、順序番号を事前に割り当てる必要が生じることがあります。そのためには、例12-6
に示すように、オーバーライドされたcreate()
メソッドでoracle.jbo.server
パッケージのSequenceImplヘルパー・クラスを使用します。これは、エンティティ・オブジェクトのカスタムJavaクラスからのコードを示しています。super.create()
をコールした後に、順序名と現在のトランザクション・オブジェクトを渡し、SequenceImpl
オブジェクトの新規インスタンスを作成します。続いてSequenceImpl
のgetSequenceNumber()
メソッドからの戻り値を含むsetWarehouseId()
属性のsetterメソッドをコールします。
例12-6 作成時に順序からの属性値のデフォルト設定
// In entity object implementation class import oracle.jbo.server.SequenceImpl; // Default WarehouseId value from WAREHOUSE_SEQ sequence at entity row create time protected void create(AttributeList attributeList) { super.create(attributeList); SequenceImpl sequence = new SequenceImpl("WAREHOUSE_SEQ",getDBTransaction()); setWarehouseId(sequence.getSequenceNumber()); }
行を保存する前に、プログラムによるデフォルト値をエンティティ・オブジェクトの属性値に割り当てる場合は、prepareForDML()
メソッドをオーバーライドし、適切な属性のsetterメソッドをコールして、導出された属性値を移入します。INSERT
、UPDATE
またはDELETE
の処理中にのみ割当てを実行するには、DML_INSERT
、DML_UPDATE
、DML_DELETE
のそれぞれの整数値の定数と、このメソッドに渡されたoperation
パラメータの値を比較できます。
例12-7は、導出値を割り当てるオーバーライドされたprepareForDML()
メソッドを示しています。
例12-7 PrepareForDMLを使用した保存前の導出値の割当て
protected void prepareForDML(int operation, TransactionEvent e) { super.prepareForDML(operation, e); //Populate GL Date if (operation == DML_INSERT) { if (this.getGlDate() == null) { String glDateDefaultOption = (String)this.getInvoiceOption().getAttribute("DefaultGlDateBasis"); if ("I".equals(glDateDefaultOption)) { setAttribute(GLDATE, this.getInvoiceDate()); } else { setAttribute(GLDATE, this.getCurrentDBDate()); } } } //Populate Exchange Rate and Base Amount if null if ((operation == DML_INSERT) || (operation == DML_UPDATE)) { BigDecimal defaultExchangeRate = new BigDecimal(1.5); if ("Y".equals(this.getInvoiceOption().getAttribute("UseForeignCurTrx"))) { if (!(this.getInvoiceCurrencyCode().equals( this.getLedger().getAttribute("CurrencyCode")))) { if (this.getExchangeDate() == null) { setAttribute(EXCHANGEDATE, this.getInvoiceDate()); } if (this.getExchangeRateType() == null) { String defaultConvRateType = (String)this.getInvoiceOption().getAttribute("DefaultConvRateType"); if (defaultConvRateType != null) { setAttribute(EXCHANGERATETYPE, defaultConvRateType); } else { setAttribute(EXCHANGERATETYPE, "User"); } } if (this.getExchangeRate() == null) { setAttribute(EXCHANGERATE, defaultExchangeRate); } if ((this.getExchangeRate() != null) && (this.getInvoiceAmount() != null)) { setAttribute(INVAMOUNTFUNCCURR, (this.getExchangeRate().multiply(this.getInvoiceAmount()))); } } else { setAttribute(EXCHANGEDATE, null); setAttribute(EXCHANGERATETYPE, null); setAttribute(EXCHANGERATE, null); setAttribute(INVAMOUNTFUNCCURR, null); } } } }
他の属性値が設定されているときに、導出された属性値を割り当てるには、最後の属性のsetterメソッドにコードを追加します。例12-8は、エンティティ・オブジェクトのAssignedTo
属性に対するsetterメソッドを示しています。
例12-8 AssignedTo属性の変更に応じて割当て日を設定するメソッド
public void setAssignedTo(Number value) { setAttributeInternal(ASSIGNEDTO, value); setAssignedDate(getCurrentDateWithTime()); }
setAttributeInternal()
をコールしてAssignedTo
属性の値を設定すると、AssignedDate
属性のsetterメソッドにより、現在日時の値が設定されます。
注意: ここで説明した生成済の属性のgetterメソッドとsetterメソッドへのカスタム・コードの追加は、安全に行うことができます。JDeveloperでクラスのコードが変更された場合でも、カスタム・コードはそのまま保持されます。 |
refresh(int flag)
メソッドを行に対して使用すると、保留中のすべての変更をリフレッシュできます。refresh()
メソッドの動作は、パラメータとして渡すフラグによって決まります。この動作を制御する3つの主要なフラグの値は、Row
インタフェースの次の定数です。
REFRESH_WITH_DB_FORGET_CHANGES
は、現在のトランザクションによる行への変更を破棄し、行データがデータベースからリフレッシュされます。行が変更されたかどうかにかかわらず、現在の行はデータベースの最新データで上書きされます。
REFRESH_WITH_DB_ONLY_IF_UNCHANGED
は、unmodified以外は、REFRESH_WITH_DB_FORGET_CHANGES
と同一の機能をします。現在のトランザクションで行がすでに変更されている場合は、行はリフレッシュされません。
REFRESH_UNDO_CHANGES
は、unmodified行に対してREFRESH_WITH_DB_FORGET_CHANGES
と同一の機能をします。変更された行については、このモードは、トランザクションの開始時点での属性値でリフレッシュします。リフレッシュ操作前に現在のトランザクション内でこの行がポスト済だったが未コミットの場合、この行は修正済状態を維持します。
デフォルトでは、refresh()
を実行したNew
状態のすべてのエンティティ行がInitialized
状態の空白行に戻ります。宣言的なデフォルト値と、initDefaultExpressionAttributes()
メソッドに記述されたプログラム的なデフォルトはリセットされますが、エンティティ・オブジェクトのcreate()
メソッドは、この無効化のプロセスでは実行されません。
このデフォルトの動作は、次の2つのフラグのうちの1つと、12.4項のフラグの1つをビット単位のOR
演算子によって組み合せて変更できます。
REFRESH_REMOVE_NEW_ROWS
: リフレッシュ時に新しい行が削除されます。
REFRESH_FORGET_NEW_ROWS
: 新しい行はDead
とマークされます。
ビジネス・ロジックでSQL問合せの実行が必要とされる場合、一般的には、そのタスクの実行にはビュー・オブジェクトを使用します。検証のために実行するSQL文では、エンティティ・ベースのビュー・オブジェクトであれば、エンティティ・キャッシュ内の保留中の変更が参照されます。読取り専用ビュー・オブジェクトでは、データベースに送信されたデータのみを取得します。
エンティティ・オブジェクトは任意のアプリケーション・シナリオで再利用されるので、特定のアプリケーション・モジュールのデータ・モデル内のビュー・オブジェクト・インスタンスに直接依存してはなりません。依存する場合、他のアプリケーション・モジュールで再使用ができないという事態が発生します。
かわりに、ビュー・アクセッサを使用して、ビュー・オブジェクトに対して検証する必要があります。詳細は、14.4.1項「エンティティ・オブジェクトまたはビュー・オブジェクトのビュー・アクセッサの作成方法」を参照してください。
例12-9に示すように、検証コードはビュー・アクセッサを使用してビュー・オブジェクトにアクセスし、バインド変数を設定します。
例12-9 メソッド・バリデータでの検証ビュー・オブジェクトの使用
// Sample entity-level validation method public boolean validateSomethingUsingViewAccessor() { RowSet rs = getMyValidationVO(); rs.setNamedBindParameter("Name1", value1); rs.setNamedBindParameter("Name2", value2); rs.executeQuery(); if ( /* some condition */) { /* * code here returns true if the validation succeeds */ } return false; }
ベスト・プラクティス: プログラミングで行セットにアクセスする際は、必ず行セットのセカンダリ・イテレータの作成を検討してください。これにより、ビュー・オブジェクトをユーザー・インタフェース・プロジェクトのデータ・コントロールとして表示する場合に使用される、デフォルト行セットイテレータの現在の行セットに混乱が生じないようにします。作業中の行セットに対して |
サンプル・コードで示すように、検証に使用されるビュー・オブジェクトには通常、1つ以上の名前付きのバインド変数が含まれます。この例では、setNamedBindParameter()
メソッドを使用してバインド変数を設定します。ただし、ビュー・アクセッサ定義用ページでGroovy式を使用して、JDeveloperで変数を宣言して設定することもできます。
ビュー・オブジェクトが取得するデータの種類によって、この例の「/* some condition */
」式は異なります。たとえば、ビュー・オブジェクトのSQL問合せがCOUNT()
などの集約関数を選択した場合、条件では一般的にrs.first()
メソッドを使用して最初の行にアクセスし、続いてgetAttribute()
メソッドを使用して属性値にアクセスし、そのカウントに対してデータベースが戻す結果を参照します。
問合せから0行または1行のどちらが戻されたかによって、検証が成功または失敗した場合、この条件はrs.first()
がnull
を戻したかどうかのみを検証します。rs.first()
がnull
を戻した場合、最初の行は存在しません。つまり、問合せで行は検出されていません。また、ビュー・オブジェクトから取得された1つ以上の問合せ結果に対し、検証の成否を繰り返し確認することもあります。
データベースに変更がポストされた後、コミットされる前に、変更保留中のリストの各エンティティに対して、beforeCommit()
メソッドがコールされます。これは、特定の型のすべてのエンティティ行に対して規則を適用する必要のある、ビュー・オブジェクト・ベースの検証を実行する場合に役立つメソッドです。
beforeCommit()
ロジックで例外ValidationException
がスローされた場合、構成中にjbo.txn.handleafterpostexc
プロパティをtrue
に設定する必要があります。これにより、フレームワークは現在のコミット・サイクルにおいて、データベースに送信済(かつコミット前)の他のエンティティ・オブジェクトのメモリー内の状態のロールバックを自動的に処理します。
たとえば、例12-10に示す、オーバーライドされたbeforeCommit()
について考えます。この例では、多相エンティティ・オブジェクトに基づいた3つのビュー・オブジェクト(Persons
、Staff
およびSupplier
)があり、それらが多相化識別子としてPersonTypeCode
属性を持っています。PersonsImpl.java
ファイルには、検証メソッドをコールするようにオーバーライドされたbeforeCommit()
メソッドがあります。この検証メソッドでは、各ユーザー・タイプを通じてプリンシパル名が一意になっていることを確認するための4番目のビュー・オブジェクト、PersonsValidator
が使用されています。たとえば、Staff
ビュー・オブジェクトのPrincipalName
としてSKING
が存在する場合、このユーザー・タイプまたは他のユーザー・タイプに別のSKING
にできません。
例12-10 特定タイプのすべてのエンティティを検証するためのbeforeCommit()のオーバーライド
// In entity object implementation class . . . @Override public void beforeCommit(TransactionEvent transactionEvent) throws ValidationException { String principalName = getPrincipalName(); if (!validatePrincipalNameIsUniqueUsingViewAccessor(principalName)) { throw new ValidationException("Principal Name must be unique across person types"); } super.beforeCommit(transactionEvent); } public boolean validatePrincipalNameIsUniqueUsingViewAccessor(String principalName) { RowSet rs = getPersonsValidatorVO(); rs.setNamedWhereClauseParam("principalName", principalName); rs.setRangeSize(-1); rs.executeQuery(); Row[] validatorRows = rs.getAllRowsInRange(); if (validatorRows.length > 1) // more than one row has the same princpalName { return false; } rs.closeRowSetIterator(); return true; }
エンティティ・オブジェクトまたはビュー・オブジェクトのビジネス・ロジックが独自のビュー・アクセッサ行セット上で反復処理を行い、そのビュー・アクセッサがモデルによって定義された値のリストでも使用されていない場合、セカンダリ行セット・イテレータを使用する必要はありません。たとえば、ある名前のバインド・パラメータを取るビュー・オブジェクトのAirportValidationVA
という名前のビュー・アクセッサがエンティティ・オブジェクトにある場合、GroovyスクリプトまたはJavaを使用して独自のアクセッサ行セットでの反復処理を行うことができます。例12-11に、ビュー・アクセッサ行セット上で反復処理を行うGroovyスクリプトを示します。
例12-11 Groovyスクリプトでのビュー・アクセッサの使用
AirportValidationVA.setNamedWhereClauseParam("VarTla",newValue) AirportValidationVA.executeQuery(); return AirportValidationVA.first() != null;
例12-12に、ビュー・アクセッサ行セット上で反復処理を行うJavaメソッド・バリデータを示します。
関連するエンティティ・オブジェクトから情報にアクセスするには、エンティティ・オブジェクトのカスタムJavaクラスにあるアソシエーション・アクセッサ・メソッドを使用します。アクセッサ・メソッドをコールすると、アソシエーションのカーディナリティに応じて、すべての関連するエンティティ行、またはエンティティ行のセットに簡単にアクセスできます。
アクセッサを使用して関連するエンティティ行にアクセスできます。例12-13は、EmpEO
エンティティ・オブジェクトのカスタムJavaクラス内のpostChanges()
メソッドのオーバーライドを示す、SummitADF
アプリケーション・ワークスペース内のcontrolpostorder
モジュールからのコードを示しています。getDeptEO()
アソシエーション・アクセッサを使用して、従業員の関連部門を取得しています。
例12-13 Createメソッドにおける親エンティティ行へのアクセス
// In EmpEOImpl.java
public void postChanges(TransactionEvent transactionEvent) {
/* If current entity is new or modified */
if (getPostState() == STATUS_NEW ||
getPostState() == STATUS_MODIFIED) {
/* Get the associated dept for the employee */
DeptEOImpl dept = getDeptEO()
;
/* If there is an associated dept */
if (dept != null) {
/* And if it's post-status is NEW */
if (dept.getPostState() == STATUS_NEW) {
/*
* Post the department first, before posting this
* entity by calling super below
*/
dept.postChanges(transactionEvent);
}
}
}
super.postChanges(transactionEvent);
}
アソシエーションのカーディナリティが、複数の行が戻されるようになっている場合、アソシエーション・アクセッサを使用してエンティティ行のセットを戻すことができます。
例12-14は、DeptEO
エンティティ・オブジェクトのカスタムJavaクラスのオーバーライドされたpostChanges()
メソッドのコードを示しています。setDeptId()
アソシエーション・アクセッサを使用して各行のDeptId
属性を更新するために、getEmpEO()
アソシエーション・アクセッサを使用してEmpEO
行のRowSet
オブジェクトを取得する方法を示しています。
例12-14 アソシエーション・アクセッサを使用した関連するエンティティ行セットへのアクセス
// In DeptEOImpl.java in the controlpostorder module // of the SummitADF application workspace RowSet newEmployeesBeforePost = null; @Override public void postChanges(TransactionEvent transactionEvent) { /* Update references only if Department is a NEW one */ if (getPostState() == STATUS_NEW) { /* * Get a rowset of employees related * to this new department before calling super */ newEmployeesBeforePost = (RowSet)getEmpEO()
; } super.postChanges(transactionEvent); } @Override protected void refreshFKInNewContainees() { if (newEmployeesBeforePost != null) { Number newDeptId = getId().getSequenceNumber(); /* * Process the rowset of employees that referenced * the new department prior to posting, and update their * Id attribute to reflect the refreshed Id value * that was assigned by a database sequence during posting. */ while (newEmployeesBeforePost.hasNext()){ EmpEOImpl emp = (EmpEOImpl)newEmployeesBeforePost.next(); emp.setDeptId(newDeptId)
; } closeNewProductRowSet(); } }
アプリケーションで「ADFセキュリティの構成」ウィザードを実行して、ADF認証サーブレットによるユーザーのログインとログアウトのサポートを有効にした場合、oracle.jbo.server.SessionImpl
オブジェクトでは、認証されたユーザーの名前と、そのユーザーの属するロールを取得するメソッドを使用できます。これは、クライアントがアクセス可能なoracle.jbo.Session
インタフェースの実装クラスです。
認証済ユーザーに関する情報へのアクセス方法の詳細は、41.11.3.3項「現在のユーザー名、テナント名またはテナントIDの特定方法」および41.11.3.4項「Java EEセキュリティ・ロールのメンバーシップ特定方法」を参照してください。
Oracle Fusion Webアプリケーションのセキュリティ機能の詳細は、第41章「Fusion WebアプリケーションでのADFセキュリティの有効化」を参照してください。
エンティティ属性値が現在のトランザクション内で変更されている場合、属性getterメソッドをその属性値に対してコールすると、この保留中の変更値が戻されます。変更前の元の値を取得する場合があります。getPostedAttribute()
メソッドを使用すると、エンティティ・オブジェクトのビジネス・ロジックで、エンティティ行が変更される前にデータベースから読み取った、属性の元の値を確認できます。このメソッドは属性indexを引数として取るので、JDeveloperに保管されている正しく生成された属性インデックス列挙を渡します。
現在のユーザー・セッションに関連した情報を、エンティティ・オブジェクトのビジネス・ロジックにより参照可能な方法で格納する必要がある場合、Session
オブジェクトで提供されるユーザー・データのハッシュ表を使用できます。
新規ユーザーがアプリケーション・モジュールに初めてアクセスする場合、prepareSession()
メソッドがコールされます。例12-15に示すように、アプリケーション・モジュールはprepareSession()
をオーバーライドし、ビュー・オブジェクト・インスタンスのretrieveUserInfoForAuthenticatedUser()
メソッドをコールして、認証されたユーザーに関する情報を取得します。次に、ユーザーの数値IDをユーザー・データ・ハッシュ表に保存するため、setUserIdIntoUserDataHashtable()
ヘルパー・メソッドをコールします。
例12-15 ユーザー情報の動問合せのためのprepareSession()のオーバーライド
// In the application module protected void prepareSession(Session session) { super.prepareSession(session); /* * Query the correct row in the VO based on the currently logged-in * user, using a custom method on the view object component */ getLoggedInUser().retrieveUserInfoForAuthenticatedUser(); setUserIdIntoUserDataHashtable(); }
例12-16に、ビュー・オブジェクトのretrieveUserInfoForAuthenticatedUser()
メソッドのコードを示します。これは、それ自体のEmailAddress
バインド変数を、セッションの認証されたユーザー名に設定し、executeQuery()
をコールしてUSERS
表から追加のユーザー情報を取得します。
例12-16 ユーザー詳細の追加取得のための、認証されたユーザー名へのアクセス
// In the view object's custom Java class public void retrieveUserInfoForAuthenticatedUser() { SessionImpl session = (SessionImpl)getDBTransaction().getSession(); setEmailAddress(session.getUserPrincipalName()); executeQuery(); first(); }
ビュー・オブジェクトが取得する、認証されたユーザーの情報の1つには、メソッドの結果として戻されるユーザーの数値ID番号があります。たとえば、ユーザーsking
には、数値のUserId
として300
が割り当てられています。
例12-17は、例12-15のprepareSession()
コードで使用される、setUserIdIntoUserDataHashtable()
ヘルパー・メソッドを示しています。このメソッドでは、文字列定数CURRENT_USER_ID
で指定されるキーを使用して、数値ユーザーIDをユーザー・データ・ハッシュ表に格納します。
例12-17 エンティティ・オブジェクトからのアクセスのためのユーザー・データ・ハッシュ表への情報の設定
// In the application module private void setUserIdIntoUserDataHashtable() { Integer userid = getUserIdForLoggedInUser(); Hashtable userdata = getDBTransaction().getSession().getUserData(); userdata.put(CURRENT_USER_ID, userid); }
この例の対応するエンティティ・オブジェクトでは、例12-18のようなヘルパー・メソッドを使用して、この数値ユーザーIDを参照するようにcreate()
メソッドをオーバーライドできます。オーバーライドされたメソッドでは、CreatedBy
属性を現在認証されているユーザーの数値ユーザーIDにプログラム的に設定します。
フレームワークでGroovyスクリプトを使用できる、オブジェクトにアクセス可能なadf
という名前のトップレベルのオブジェクトが用意されています。adf.userSession
オブジェクトは、ADFビジネス・コンポーネント・ユーザー・セッションへの参照を返します。これを使用してセッションに含まれているuserData
ハッシュ・マップ内の値を参照できます。
例12-19に、MyKey
という名前のuserData
ハッシュ・マップ・キーの参照に使用するGroovyスクリプトを示します。
エンティティ・オブジェクトのビジネス・ロジックにおいては、現在の日時の参照が役立つ場合があります。次のGroovyスクリプト式を使用すると、現在の日付または現在の日付と時間を参照できます。
adf.currentDate
- 現在の日付(時間を切捨て)を戻します。
adf.currentDateTime
- 現在の日付と時間を戻します。
エンティティ・オブジェクトのビジネス・ロジックでのGroovyスクリプトの使用方法の詳細は、3.5.6項「ビジネス・コンポーネントでのGroovyスクリプト言語の使用方法」を参照してください。
変更保留中のリストに含まれていた各エンティティ行に対してafterCommit()
メソッドがコールされ、データベースに正しく格納されます。このメソッドを使用して、コミットに関する通知を送信できます。
コミット成功時に通知を送信するもっとよい方法は、ビジネス・イベントを宣言する方法です。ビジネス・イベントの作成方法の詳細は、4.12項「ビジネス・イベントの作成」を参照してください。
remove()
メソッドは、エンティティ行削除の前に、その行に対して起動します。remove()
メソッドでは、JboException
をスローし、該当する条件を満たさない場合は行の削除を回避するよう設定できます。
たとえばremove()
メソッド内で、エンティティ・オブジェクトの状態を判断するテストを追加し、それが新規レコードの場合にのみ削除可能にすることができます。例12-20に、この方法を示します。
例12-20 remove()メソッドのオーバーライドによる削除前のエンティティ状態の確認
// In entity object custom Java class private boolean isDeleteAllowed() { byte s = this.getEntityState(); return s==STATUS_NEW; } /** * Add entity remove logic in this method. */ public void remove() { if (isDeleteAllowed()) super.remove(); else throw new JboException("Delete not allowed in this view"); }
注意: このエンティティ・オブジェクトでは、既存の構成される側の子行がある場合、マスター・エンティティ行の削除が宣言的に回避されます。このオプションは、アソシエーションの概要エディタの「関連」ページで設定します。 |
エンティティ・オブジェクト・クラス内のisAttributeUpdateable()
メソッドをオーバーライドすると、特定の属性が更新可能かどうかを、実行時に適切な条件に従ってプログラム的に判断できます。
例12-21は、現在の認証されたユーザーがスタッフ・メンバーである場合にのみPersonTypeCode
属性を更新可能にするため、エンティティ・オブジェクトによりisAttributeUpdateable()
がどのようにオーバーライドされるかを示しています。エンティティ・オブジェクトでは、このメソッドを実行する際、更新可能性の確認対象となる整数属性索引を渡します。
特定の属性に対する条件付きの更新可能性ロジックは、属性索引に基づいて、if
文またはswitch
文内に実装できます。ここでは、PERSONTYPECODE
がエンティティ・オブジェクトのカスタムJavaクラスに保持されている整数属性の索引列挙を参照します。
例12-21 属性の更新可能性の実行時の条件付きの判断
// In the entity object custom Java class public boolean isAttributeUpdateable(int index) { if (index == PERSONTYPECODE) { if (!currentUserIsStaffMember()) { return super.isAttributeUpdateable(index); } return CUSTOMER_TYPE.equals(getPersonTypeCode()) ? false : true; } return super.isAttributeUpdateable(index); }
注意: エンティティ・ベースのビュー・オブジェクトは、エンティティ・オブジェクト内でカプセル化された他の要素と同様に、この条件付きの更新可能性も継承します。この種の条件付き更新可能性ロジックを、一時ビュー・オブジェクト属性に固有の方法で実装する必要がある場合、または、ビュー・オブジェクト内の複数のエンティティ・オブジェクトのデータを含む一部の条件を適用する場合、ビュー・オブジェクトのビュー行クラス内の同じメソッドをオーバーライドすると、目的の結果が得られます。 |
ADFビジネス・コンポーネントには、開発者が使用できる組込みの宣言的検証規則の基本セットが付属しています。ただし、エンティティ・オブジェクト用のバリデータ・アーキテクチャの強力な機能は、独自のカスタム検証規則を作成できることです。同じ種類の検証コードを繰り返し作成している場合は、カスタム検証規則クラスを作成し、このような共通の検証パターンをパラメータ化された方法で取得できます。
定義したカスタム検証規則クラスは、JDeveloperに登録し、組込み規則と同じように簡単に使用できます。実際、カスタム検証規則をカスタムUIパネルにバンドルすることもできます。JDeveloperはこれを利用して、検証規則で必要とするパラメータを開発者が簡単に使用および構成できるようにします。
エンティティ・オブジェクト用のカスタム検証規則を作成するには、oracle.jbo.rules
パッケージのJboValidatorInterface
を実装するJavaクラスが必要です。「新規ギャラリ」からスケルトン・クラスを作成できます。
始める前に:
カスタム検証規則に関する知識があると役立つ場合があります。詳細は、12.14項「カスタム検証規則の実装」を参照してください。
また、他の検証機能を使用して追加できる機能についても理解しておくと役立ちます。詳細は、12.1.2項「プログラム的なビジネス・ルールの追加機能」を参照してください。
カスタム・バリデータを作成するには:
「アプリケーション」ウィンドウで、バリデータを作成するプロジェクトを右クリックし、「新規」→「ギャラリから」を選択します。
「新規ギャラリ」で、「ビジネス層」を開き、「ADFビジネス・コンポーネント」を選択してから、「検証ルール」を選択し、「OK」をクリックします。
「検証ルール・クラスの作成」ダイアログで、ルールの名前とパッケージを入力します。
表示名と説明を入力して、「OK」をクリックします。
例12-22に示すように、JBOValidatorInterface
には、1つのメインvalidate()
メソッドと、Description
プロパティ用のgetterおよびsetterメソッドが含まれます。
例12-22 すべての検証規則が実装する必要のあるJboValidatorInterface
package oracle.jbo.rules; public interface JboValidatorInterface { void validate(JboValidatorContext valCtx) { } java.lang.String getDescription() { } void setDescription(String description) { } }
検証規則の動作がパラメータ化されて柔軟性が増してから、検証クラスの各パラメータにBeanプロパティを追加します。たとえば、例12-23のコードにはDateMustComeAfterRule
という名前のカスタム検証規則が含まれており、ある日付属性が別の日付属性より後でなければならないことを検証します。規則を使用する開発者が検証対象の2つの日付として使用する日付属性の名前を構成できるように、このクラスではinitialDateAttrName
とlaterDateAttrName
という2つのプロパティが定義されています。
例12-23は、カスタム検証規則を実装するコードを示しています。これは、AbstractValidator
を拡張し、エンティティ・オブジェクトのカスタム・メッセージ・バンドルの使用を継承しています。開発者がエンティティ・オブジェクトの規則を使用すると、JDeveloperは検証エラー・メッセージをここに保存します。
検証規則のvalidate()
メソッドは、実行時に規則クラスが機能を実行する必要がある場合に常に呼び出されます。このコードが実行する基本的な手順は次のとおりです。
エンティティ・レベルでバリデータが正しくアタッチされていることを確認します。
検証するエンティティ行を取得します。
先の日と後の日の属性の値を取得します。
先の日が後の日より前であることを検証します。
検証が失敗した場合は例外をスローします。
例12-23 カスタムのDateMustComeAfterRule
// package and imports omitted public class DateMustComeAfterRule extends AbstractValidator implements JboValidatorInterface { /** * This method is invoked by the framework when the validator should do its job */ public void validate(JboValidatorContext valCtx) { // 1. If validator is correctly attached at the entity level... if (validatorAttachedAtEntityLevel(valCtx)) { // 2. Get the entity row being validated EntityImpl eo = (EntityImpl)valCtx.getSource(); // 3. Get the values of the initial and later date attributes Date initialDate = (Date) eo.getAttribute(getInitialDateAttrName()); Date laterDate = (Date) eo.getAttribute(getLaterDateAttrName()); // 4. Validate that initial date is before later date if (!validateValue(initialDate,laterDate)) { // 5. Throw the validation exception RulesBeanUtils.raiseException(getErrorMessageClass(), getErrorMsgId(), valCtx.getSource(), valCtx.getSourceType(), valCtx.getSourceFullName(), valCtx.getAttributeDef(), valCtx.getNewValue(), null, null); } } else { throw new RuntimeException("Rule must be at entity level"); } } /** * Validate that the initialDate comes before the laterDate. */ private boolean validateValue(Date initialDate, Date laterDate) { return (initialDate == null) || (laterDate == null) || (initialDate.compareTo(laterDate) < 0); } /** * Return true if validator is attached to entity object * level at runtime. */ private boolean validatorAttachedAtEntityLevel(JboValidatorContext ctx) { return ctx.getOldValue() instanceof EntityImpl; } // NOTE: Getter and Setter Methods omitted private String description; private String initialDateAttrName; private String laterDateAttrName; }
カスタム検証規則を簡単に再利用できるように、通常は、規則を利用するアプリケーションでの参照用として規則をJARファイルにパッケージします。
検証規則クラスはBeanであるため、標準のJavaBeanカスタマイザ・クラスを実装して、設計時にBeanのプロパティを設定しやすくすることができます。例12-23
のDateMustComeAfter規則の例では、開発者はinitialDateAttrName
とlaterDateAttrName
の2つのプロパティを構成する必要があります。
図12-4は、Swing用のJDeveloperビジュアル・デザイナを使用して作成されたDateMustComeAfterRuleCustomizer
を示しており、タイトル付きの境界を持つJPanel
に、ドロップダウン・リスト用の2つのJLabel
プロンプトと2つのJComboBox
コントロールが含まれています。クラスのコードによって、IDEで現在編集されているエンティティ・オブジェクトのDate値属性の名前がドロップダウン・リストに移入されます。これにより、DateMustComeAfterRule
検証をエンティティ・オブジェクトに追加する開発者は、検証の開始日と終了日に使用する日付属性を簡単に選択できます。
カスタマイザとDateMustComeAfterRule
Java Beanを関連付けるには、BeanInfo
クラスを作成するときの標準的な手順に従います。例12-24で示されているように、DateMustComeAfterRuleBeanInfo
は、カスタマイザ・クラスをDateMustComeAfter
Beanクラスと関連付けるBeanDescriptor
を返します。
通常は、カスタマイザ・クラスとこのBean情報を、設計時専用の独立したJARファイルにパッケージします。
例12-24 カスタマイザとカスタム検証規則を関連付けるためのBeanInfo
package oracle.summit.model...frameworkExt.rules; import java.beans.BeanDescriptor; import java.beans.SimpleBeanInfo; public class DateMustComeAfterRuleBeanInfo extends SimpleBeanInfo { public BeanDescriptor getBeanDescriptor() { return new BeanDescriptor(DateMustComeAfterRule.class, DateMustComeAfterRuleCustomizer.class); } }
カスタム検証規則は作成後、JDeveloper IDEのプロジェクトまたはアプリケーションレベルに追加できるため、他の開発者がその規則を宣言的に使用できます。
カスタム検証規則をプロジェクト・レベルで登録した場合は、プロジェクト内でその検証規則を使用できます。
始める前に:
カスタム検証規則に関する知識があると役立つ場合があります。詳細は、12.14項「カスタム検証規則の実装」を参照してください。
また、他の検証機能を使用して追加できる機能についても理解しておくと役立ちます。詳細は、12.1.2項「プログラム的なビジネス・ルールの追加機能」を参照してください。
カスタム検証規則を登録するには、12.14.1項「カスタム検証規則の作成方法」で説明しているように、先にその検証規則を作成しておく必要があります。
エンティティ・オブジェクトを含むプロジェクトでカスタム検証規則を登録するには:
「アプリケーション」ウィンドウで、エンティティ・オブジェクトを含むプロジェクトを右クリックし、「プロジェクト・プロパティ」を選択します。
「プロジェクト・プロパティ」ダイアログで、「ADFビジネス・コンポーネント」を開き、「登録済ルール」を選択します。
「登録済の規則」ページで、「追加」をクリックします。
「検証ルールの登録」ダイアログで、作成した検証ルールを参照して検索し、「OK」をクリックします。
カスタム検証規則をJDeveloperのIDEレベルで登録すると、現在のプロジェクトのみでなく、他のプロジェクトでもその検証規則を使用できます。
始める前に:
カスタム検証規則に関する知識があると役立つ場合があります。詳細は、12.14項「カスタム検証規則の実装」を参照してください。
また、他の検証機能を使用して追加できる機能についても理解しておくと役立ちます。詳細は、12.1.2項「プログラム的なビジネス・ルールの追加機能」を参照してください。
カスタム検証規則を登録するには、12.14.1項「カスタム検証規則の作成方法」で説明しているように、先にその検証規則を作成しておく必要があります。
IDEレベルのカスタム・バリデータを登録するには:
メイン・メニューから、「ツール」→「プリファレンス」を選択します。
「プリファレンス」ダイアログで、「ADFビジネス・コンポーネント」を開き、「ルールの登録」を選択します。
「ルールの登録」ページから、1つ以上の検証規則を追加できます。
検証ルールを追加するときは、検証ルール・クラスの完全修飾名を指定し、JDeveloperの使用可能なバリデータのリストで表示される検証ルールの名前を設定します。