12 プログラムによる検証とビジネス・ルールの実装
この章の内容は次のとおりです。
プログラム的なビジネス・ルールについて
ADFビジネス・コンポーネントには、組込み宣言型検証機能の使用に加えて、ビジネス・ロジックをプログラムで定義するためのメソッド・バリデータおよびイベントが用意されています。
内蔵された宣言型検証機能を補完するため、エンティティ・オブジェクトとビュー・オブジェクトにはJavaコードを使用してカプセル化されたビジネス・ロジックをプログラミングによって実装できる操作可能なメソッド・バリデータといくつかのイベントがあります。これらの概念を、図12-1に示します。
-
属性レベルのメソッド・バリデータは、属性値が変更されると、検証コードをトリガーします。
-
エンティティ・レベルのメソッド・バリデータは、エンティティ行が検証されると、検証コードをトリガーします。
-
あるエンティティに対して、次の主要メソッドをカスタムJavaクラスでオーバーライドできます。
-
create()
: 行の作成時にデフォルト値を割り当てます。 -
initDefaultExpressionAttributes()
: 行の作成時、または新規行のリフレッシュ時にデフォルト値を割り当てます。 -
remove()
: 条件付きで削除を制限します。 -
isAttributeUpdateable()
: 属性を条件付きで更新可能にします。 -
setAttribute()
: 属性レベルのメソッド・バリデータをトリガーします。 -
validateEntity()
: エンティティ・レベルのメソッド・バリデータをトリガーします。 -
prepareForDML()
: エンティティ行が保存される前に属性値を割り当てます。 -
beforeCommit()
: 指定された型のすべてのエンティティ行に関連する規則を実行します。 -
afterCommit()
: エンティティ・オブジェクトの状態の変更に関する通知を送信します。
-
図12-1 プログラム的なビジネス・ロジックにおける主要なエンティティ・オブジェクトの機能とイベント
![図12-1の説明が続きます 図12-1の説明が続きます](img/adffd_jd_005.png)
「図12-1 プログラム的なビジネス・ロジックにおける主要なエンティティ・オブジェクトの機能とイベント」の説明
ノート:
新規行と変更された行のみがトランザクション検証の対象となります。削除された行はトランザクションの一部ですが、検証には含まれません。
プログラム的なビジネス・ルールをコーディングする場合、検証サイクルを正確に把握しておくことが重要です。「検証サイクルの理解」を参照してください。
プログラム的なビジネス・ルールのユースケースと例
ほとんどの検証は基本的な宣言的操作で実装できますが、メソッド・バリデータを使用してカスタム検証コードを起動することにより、必要に応じてビジネス・ドメイン・レイヤーにより複雑なビジネス・ルールを実装できます。
プログラム的なビジネス・ルールを使用する場合としては、次の例があります。
-
データベース順序からの属性値のデフォルト設定
-
複雑な計算から導出された値の割当て
-
エンティティ・オブジェクトに対する保留中の変更の取消し
-
現在のユーザー・セッションに関する情報のアクセスと格納
-
属性の条件付き更新可能性の判断
プログラム的なビジネス・ルールの追加機能
プログラム的な検証を使用する前に、他のOracle ADF機能を理解しておくと役立つ場合があります。次に、関連する他の機能へのリンクを示します。
-
ビジネス・ルールをプログラム的に実装する前に、宣言的検証でアプリケーションのニーズに対応できるかどうかを確認する必要があります。詳細は、「検証とビジネス・ルールの宣言的な定義」を参照してください。
-
リソース・バンドルを使用して、ローカライズ可能な検証エラー・メッセージを用意できます。「リソース・バンドルの使用」を参照してください。
-
「トリガーによって割り当てられた値を同期化する方法」で、主キーの属性に
DBSequence
型を使用する方法を説明しています。この種の属性の値は、コミット時にデータベース・シーケンスによって取り込む必要があります。 -
認証されたユーザーに関する情報へのアクセス方法など、Oracle Fusion Webアプリケーションのセキュリティ機能の詳細は、「Fusion WebアプリケーションでのADFセキュリティの有効化」を参照してください。
-
エンティティ・オブジェクトのビジネス・ロジックでのGroovyスクリプトの使用方法の詳細は、「ビジネス・コンポーネントでのGroovyスクリプト言語の使用」を参照してください。
メソッド・バリデータの使用
メソッド・バリデータによってトリガーされるカスタムJavaコードを定義できます。ADFメソッド・バリデータを使用すると、宣言的検証規則を補完できます。属性またはエンティティ・レベルのメソッド・バリデータを定義できます。
メソッド・バリデータは、独自のJavaコードを使用した宣言的な検証規則とGroovyスクリプト式を補完する主要な手段です。メソッド・バリデータでは、独自の検証メソッド内に記述したJavaコードが、エンティティ・オブジェクト検証サイクルにおいて適切なタイミングでトリガーされます。メソッド・バリデータにより、属性またはエンティティ全体で、様々な型の検証をコーディングできます。
それぞれがコード内の異なるメソッド名をトリガーしていれば、任意の数の属性レベルまたはエンティティレベルのメソッドを追加できます。すべての検証メソッド名はvalidate
というワードで開始する必要があります。ただし、この規則にさえ従っていれば、機能を明確に示す任意の方法で名前を付けることができます。属性レベルのバリデータでは、メソッドはエンティティ属性と同じ型の引数を1つだけ取ります。エンティティレベルのバリデータでは、メソッドは引数を取りません。メソッドはパブリックで、ブール値を返す必要があります。メソッドからfalse
が返されると、検証は失敗します。
ノート:
これらの規則を知っておくことは必要ですが、JDeveloperを使用してメソッド・バリデータを作成する場合、そのクラスの正しいインタフェースが作成されます。
実行時には、メソッド・バリデータにより、エンティティ属性がエンティティ・オブジェクト・クラスで実装されているメソッドに渡されます。
次の例では、メソッドは大文字で始まり、NULL値で例外をスローする文字列、空の文字列、および大文字で始まらない文字列を受け取ります。
public boolean validateIsCapped(String text) { if (text != null && text.length() != 0 && text[0] >= 'A' && text[0] <= 'Z') { return true; } }
属性レベルのメソッド・バリデータの作成方法
メソッド・バリデータの使用は、宣言的検証規則をプログラム的に補完する手段です。
始める前に:
メソッド・バリデータに関する知識があると役立つ場合があります。詳細は、「メソッド・バリデータの使用」を参照してください。
また、他の検証機能を使用して追加できる機能についても理解しておくと役立ちます。詳細は、「プログラム的なビジネス・ルールの追加機能」を参照してください。
属性レベルのメソッド・バリデータを作成するには:
属性レベルのメソッド・バリデータを作成するときに行われる処理
メソッド・バリデータを新たに追加すると、JDeveloperでは新しい検証ルールを反映するように、XMLドキュメントが更新されます。メソッドの作成を指定した場合、そのメソッドはエンティティ・オブジェクトのカスタムJavaクラスに追加されます。次の例は、注文のOrderShippedDate
が現在月の日付であることを確認する、簡単な属性レベルの検証ルールを示しています。メソッドには、対応する属性と同じ型の引数が与えられ、その条件付きのロジックはこの入力パラメータの値に基づいています。属性バリデータの起動時、属性値は対象となる新しい新しい値にはまだ設定されていません。したがって、getOrderShippedDate()
メソッドをOrderShippedDate
属性に対する属性バリデータ内でコールしても、クライアントが設定しようとしている候補値ではなく、属性の現在の値が返されます。
ノート:
compareTo()
メソッドの戻り値は、2つの日付が等しい場合はゼロ(0
)、最初の日付が次の日付より前の場合はマイナス1(-1
)、最初の日付が次の日付より後の場合はプラス1 (1
)となります。
public boolean validateOrderShippedDate(Date data) { if (data != null && data.compareTo(getFirstDayOfCurrentMonth()) <= 0) { return false; } return true; }
エンティティ・レベルのメソッド・バリデータの作成方法
エンティティ・レベルのメソッド・バリデータは、有効範囲がより広く、単一の属性ではなくエンティティ全体が範囲になる点を除けば、属性レベルのメソッド・バリデータと似ています。
さらに、エンティティレベルのメソッド・バリデータを使用している場合は、バリデータの実行をトランザクションの時間まで遅延できます。
始める前に:
メソッド・バリデータに関する知識があると役立つ場合があります。詳細は、「メソッド・バリデータの使用」を参照してください。
また、他の検証機能を使用して追加できる機能についても理解しておくと役立ちます。詳細は、「プログラム的なビジネス・ルールの追加機能」を参照してください。
エンティティ・レベルのメソッド・バリデータを作成するには:
エンティティ・レベルのメソッド・バリデータを作成するときに行われる処理
メソッド・バリデータを新たに追加すると、JDeveloperでは新しい検証ルールを反映するように、XMLドキュメントが更新されます。メソッドの作成を指定した場合、そのメソッドはエンティティ・オブジェクトのカスタムJavaクラスに追加されます。次の例は、注文のDateShipped
がDateOrdered
より後であることを確認する、簡単なエンティティ・レベルの検証ルールを示しています。
public boolean validateDateShippedAfterDateOrdered() { Date DateShipped = getDateShipped(); Date DateOrdered = getDateOrdered(); if (DateShipped != null && DateShipped.compareTo(DateOrdered) < 0) { return false; } return true; }
「検証ルールの追加」ダイアログの「検証実行」タブで「トランザクション・レベルの遅延実行」を選択し、エンティティ・オブジェクトを表示するページでSkipValidation="skipDataControls"
と設定した場合は、トランザクションの時間まで検証を延期できます。これにより、1つの表で複数の行を変更してから、ユーザーのコミット時にそれらをグループとして検証できます。次の例は、エンティティ行の集合の部門名がNULLでないことを検証するメソッド・バリデータを示しています。
// 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コントロールのヒントと同様に、検証規則のエラー・メッセージは、エンティティ・オブジェクトのコンポーネント・メッセージ・バンドル・ファイルに追加されます。メッセージ・バンドル内のこれらのエンティティは、アプリケーションのデフォルトのロケールの文字列を表します。検証エラー・メッセージの翻訳バージョンについては、「リソース・バンドルの使用」で説明したUIコントロール・ヒントの翻訳と同様のステップで追加してください。
プログラム的な導出属性値の割当て
ADFビジネス・コンポーネントを使用すると、宣言的デフォルト設定では不十分な場合にプログラムによるデフォルト設定を実行できます。
宣言的なデフォルト設定では不十分なとき、次の場合にエンティティ・オブジェクトでプログラムによるデフォルト設定を実行できます。
-
エンティティ行が最初に作成される場合
-
エンティティ行が最初に作成される場合、またはリフレッシュされてNULL値に設定される場合
-
エンティティ行がデータベースに保存される場合
-
エンティティ属性値が設定される場合
新しい行に対する作成時のデフォルト値の指定方法
create()
メソッドは、エンティティ行の最初の作成時にデフォルト値の初期化処理を可能にするエンティティ・オブジェクト・イベントを提供します。次の例は、エンティティ・オブジェクトの作成メソッドのオーバーライドを示しています。このメソッドは、新しい注文エンティティ行に、DateOrdered
属性を移入する属性のsetterメソッドをコールします。
デフォルト値は、Groovy式を使用して定義することもできます。詳細は、「静的なデフォルト値を定義する方法」を参照してください。
ノート:
オーバーライドされたcreate()
メソッド内でsetAttribute()
メソッドをコールした場合、新規行はユーザー変更の行としてマークされません。このようにプログラム的に割り当てられたデフォルト値は、宣言的に割り当てられたデフォルト値のように動作します。また、create()
メソッドに加えた変更はすべてsuper.create()
のコール後に実行される点にも注意してください。
// In entity object implementation class protected void create(AttributeList nameValuePair) { super.create(nameValuePair); this.setDateOrdered(new Date()); }
create()メソッドとinitDefaultExpressionAttributes()メソッドのいずれかの選択
行が最初に作成されたとき、および初期化状態にリフレッシュされたときの両方で実行するプログラム的なデフォルト設定ロジックでは、initDefaultExpressionAttributes()
メソッドをオーバーライドする必要があります。
エンティティ行がNew
状態であり、そのエンティティ行に対してrefresh()
メソッドをコールした場合、REFRESH_REMOVE_NEW_ROWS
フラグまたはREFRESH_FORGET_NEW_ROWS
フラグを指定しないと、そのエンティティ行はInitialized
状態に戻ります。このプロセスの一環として、エンティティ・オブジェクトのinitDefaultExpressionAttributes()
メソッドが実行されますが、create()
メソッドは再度実行されません。
データベース順序からの属性値のデフォルト設定
「トリガーによって割り当てられた値を同期化する方法」で、主キーの属性にDBSequence
型を使用する方法を説明しています。この種の属性の値は、コミット時にデータベース・シーケンスによって取り込む必要があります。エンティティ行の作成時にユーザーが値を参照でき、この値がデータ保存時に変更されないように、順序番号を事前に割り当てる必要が生じることがあります。そのためには、次の例に示すように、オーバーライドされたcreate()
メソッドでoracle.jbo.server
パッケージのSequenceImpl
ヘルパー・クラスを使用します。これは、エンティティ・オブジェクトのカスタムJavaクラスからのコードを示しています。super.create()
をコールした後に、順序名と現在のトランザクション・オブジェクトを渡し、SequenceImpl
オブジェクトの新規インスタンスを作成します。続いてSequenceImpl
のgetSequenceNumber()
メソッドからの戻り値を含むsetWarehouseId()
属性のsetterメソッドをコールします。
ノート:
このアプローチに対するメタデータ駆動型の代替の詳細は、「Oracle順序を使用した主キー値の割当て」を参照してください。
// 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
パラメータの値を比較できます。
次の例は、導出値を割り当てるオーバーライドされた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メソッドにコードを追加します。次の例は、エンティティ・オブジェクトのAssignedTo
属性に対するsetterメソッドを示しています。
setAttributeInternal()
をコールしてAssignedTo
属性の値を設定すると、AssignedDate
属性のsetterメソッドにより、現在日時の値が設定されます。
ノート:
ここで説明した生成済の属性のgetterメソッドとsetterメソッドへのカスタム・コードの追加は、安全に行うことができます。JDeveloperでクラスのコードが変更された場合でも、カスタム・コードはそのまま保持されます。
public void setAssignedTo(Number value) { setAttributeInternal(ASSIGNEDTO, value); setAssignedDate(getCurrentDateWithTime()); }
refreshメソッドを使用した、エンティティに対する保留中の変更の取消し
refresh(int flag)
メソッドをADFビジネス・コンポーネントの行に対して使用して保留中の行変更をリフレッシュします。int
flag引数により、このメソッドの動作を制御する様々な値を使用できます。
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つと、「refreshメソッドを使用した、エンティティに対する保留中の変更の取消し」のフラグの1つをビット単位のOR
演算子によって組み合せて変更できます。
-
REFRESH_REMOVE_NEW_ROWS
: リフレッシュ時に新しい行が削除されます。 -
REFRESH_FORGET_NEW_ROWS
: 新しい行はDead
とマークされます。
構成される側の子エンティティ行へのリフレッシュのカスケード方法
REFRESH_CONTAINEES
フラグと、「refreshメソッドを使用した、エンティティに対する保留中の変更の取消し」および「リフレッシュ中の新規行の動作の制御方法」で説明した有効なフラグの組合せをビット単位のOR
演算子を使用して、構成される側の子エンティティ行をrefresh()
操作でカスケードできます。このため、エンティティにより、構成される側の子エンティティに対し、同じモードでrefresh()
が実行されます。
検証でのビュー・オブジェクトの使用
読取り専用のビュー・オブジェクトでデータの取得のみを行う間、エンティティ・キャッシュ内でADFエンティティ・ベースのビュー・オブジェクトに関する保留中の変更を管理できます。
ビジネス・ロジックでSQL問合せの実行が必要とされる場合、一般的には、そのタスクの実行にはビュー・オブジェクトを使用します。検証のために実行するSQL文では、エンティティ・ベースのビュー・オブジェクトであれば、エンティティ・キャッシュ内の保留中の変更が参照されます。読取り専用ビュー・オブジェクトでは、データベースに送信されたデータのみを取得します。
ビュー・オブジェクトに対する検証用ビュー・アクセッサの使用方法
エンティティ・オブジェクトは任意のアプリケーション・シナリオで再利用されるので、特定のアプリケーション・モジュールのデータ・モデル内のビュー・オブジェクト・インスタンスに直接依存してはなりません。依存する場合、他のアプリケーション・モジュールで再使用ができないという事態が発生します。
かわりに、ビュー・アクセッサを使用して、ビュー・オブジェクトに対して検証する必要があります。詳細は、「エンティティ・オブジェクトまたはビュー・オブジェクトのビュー・アクセッサの作成方法」を参照してください。
次の例に示すように、検証コードはビュー・アクセッサを使用してビュー・オブジェクトにアクセスし、バインド変数を設定します。
// 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; }
ベスト・プラクティス:
プログラミングで行セットにアクセスする際は、必ず行セットのセカンダリ・イテレータの作成を検討してください。これにより、ビュー・オブジェクトをユーザー・インタフェース・プロジェクトのデータ・コントロールとして表示する場合に使用される、デフォルト行セット・イテレータの現在の行セットに混乱が生じないようにします。作業中の行セットに対してcreateRowSetIterator()
をコールし、セカンダリとして指定する行セット・イテレータを作成します。プログラミングによる反復を修了すると、コードは行セットに対してcloseRowSetIterator()
をコールし、セカンダリ・イテレータをメモリーから削除する必要があります。
サンプル・コードで示すように、検証に使用されるビュー・オブジェクトには通常、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
に設定する必要があります。これにより、フレームワークは現在のコミット・サイクルにおいて、データベースに送信済(かつコミット前)の他のエンティティ・オブジェクトのメモリー内の状態のロールバックを自動的に処理します。
たとえば、次の例に示す、オーバーライドされたbeforeCommit()
について考えます。この例では、多相エンティティ・オブジェクトに基づいた3つのビュー・オブジェクト(Persons
、Staff
およびSupplier
)があり、それらが多相化識別子としてPersonTypeCode
属性を持っています。PersonsImpl.java
ファイルには、検証メソッドをコールするようにオーバーライドされたbeforeCommit()
メソッドがあります。この検証メソッドでは、各ユーザー・タイプを通じてプリンシパル名が一意になっていることを確認するための4番目のビュー・オブジェクト、PersonsValidator
が使用されています。たとえば、Staff
ビュー・オブジェクトのPrincipalName
としてSKING
が存在する場合、このユーザー・タイプまたは他のユーザー・タイプに別のSKING
にできません。
// 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を使用して独自のアクセッサ行セットでの反復処理を行うことができます。次の例は、ビュー・アクセッサ行セット上で反復処理を行うGroovyスクリプトを示しています。
AirportValidationVA.setNamedWhereClauseParam("VarTla",newValue) AirportValidationVA.executeQuery(); return AirportValidationVA.first() != null;
次の例は、ビュー・アクセッサ行セット上で反復処理を行うJavaメソッド・バリデータを示しています。
public boolean validateJob(String job) { getAirportValidationVA().setNamedWhereClauseParam("VarTla",job); getAirportValidationVA().executeQuery(); return getAirportValidationVA().first() != null; }
アソシエーション・アクセッサを使用した、関連するエンティティ行へのアクセス
ADFエンティティ・オブジェクトのカスタムJavaクラスのアソシエーション・アクセッサ・メソッドを使用して、関連するエンティティ・オブジェクトからの情報にアクセスします。
関連するエンティティ・オブジェクトから情報にアクセスするには、エンティティ・オブジェクトのカスタムJavaクラスにあるアソシエーション・アクセッサ・メソッドを使用します。アクセッサ・メソッドをコールすると、アソシエーションのカーディナリティに応じて、すべての関連するエンティティ行、またはエンティティ行のセットに簡単にアクセスできます。
関連するエンティティ行へのアクセス方法
アクセッサを使用して関連するエンティティ行にアクセスできます。次の例は、EmpEO
エンティティ・オブジェクトのカスタムJavaクラス内のオーバーライドされたpostChanges()
メソッドを示す、SummitADF
アプリケーション・ワークスペース内のcontrolpostorder
モジュールからのコードを示しています。getDeptEO()
アソシエーション・アクセッサを使用して、従業員の関連部門を取得しています。
// 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);
}
関連するエンティティ行セットへのアクセス方法
アソシエーションのカーディナリティが、複数の行が戻されるようになっている場合、アソシエーション・アクセッサを使用してエンティティ行のセットを戻すことができます。
次の例は、DeptEO
エンティティ・オブジェクトのカスタムJavaクラス内のオーバーライドされたpostChanges()
メソッドのコードを示しています。setDeptId()
アソシエーション・アクセッサを使用して各行のDeptId
属性を更新するために、getEmpEO()
アソシエーション・アクセッサを使用してEmpEO
行のRowSet
オブジェクトを取得する方法を示しています。
// 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認証サーブレットでWebアプリケーション・コンテナ別にユーザー・ログインおよびユーザー・ログアウトをトリガーできるようにします。認証されたユーザー資格証明を取得することもできます。
アプリケーションで「ADFセキュリティの構成」ウィザードを実行して、ADF認証サーブレットによるユーザーのログインとログアウトのサポートを有効にした場合、oracle.jbo.server.SessionImpl
オブジェクトでは、認証されたユーザーの名前と、そのユーザーの属するロールを取得するメソッドを使用できます。これは、クライアントがアクセス可能なoracle.jbo.Session
インタフェースの実装クラスです。
認証されたユーザーに関する情報へのアクセス方法の詳細は、「Java EEセキュリティ・ロールのメンバーシップの判別方法」を参照してください。
Oracle Fusion Webアプリケーションのセキュリティ機能の詳細は、「Fusion WebアプリケーションでのADFセキュリティの有効化」を参照してください。
元の属性値へのアクセス
getPostedAttribute()
メソッドを使用して、ADFエンティティ・オブジェクト属性の元の値(現行トランザクションで変更される前のエンティティ行での値)を取得します。
エンティティ属性値が現在のトランザクション内で変更されている場合、属性getterメソッドをその属性値に対してコールすると、この保留中の変更値が戻されます。変更前の元の値を取得する場合があります。getPostedAttribute()
メソッドを使用すると、エンティティ・オブジェクトのビジネス・ロジックで、エンティティ行が変更される前にデータベースから読み取った、属性の元の値を確認できます。このメソッドは属性indexを引数として取るので、JDeveloperに保管されている正しく生成された属性インデックス列挙を渡します。
現在のユーザー・セッションに関する情報の格納
Sessionオブジェクトによって提供されるユーザー・データ・ハッシュ表を使用して、ADFエンティティ・オブジェクト・ビジネス・ロジックで利用できる現行ユーザー・セッション情報を格納します。
現在のユーザー・セッションに関連した情報を、エンティティ・オブジェクトのビジネス・ロジックにより参照可能な方法で格納する必要がある場合、Session
オブジェクトで提供されるユーザー・データのハッシュ表を使用できます。
現在のユーザー・セッションに関する情報の格納方法
新規ユーザーがアプリケーション・モジュールに初めてアクセスする場合、prepareSession()
メソッドがコールされます。次の例に示すように、アプリケーション・モジュールはprepareSession()
をオーバーライドし、ビュー・オブジェクト・インスタンスのretrieveUserInfoForAuthenticatedUser()
メソッドをコールして、認証されたユーザーに関する情報を取得します。次に、ユーザーの数値IDをユーザー・データ・ハッシュ表に保存するため、setUserIdIntoUserDataHashtable()
ヘルパー・メソッドをコールします。
// 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(); }
次の例は、ビュー・オブジェクトのretrieveUserInfoForAuthenticatedUser()
メソッドのコードを示しています。これは、それ自体のEmailAddress
バインド変数を、セッションの認証されたユーザー名に設定し、executeQuery()
をコールしてUSERS
表から追加のユーザー情報を取得します。
// 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
が割り当てられています。
次の例は、前述の例のprepareSession()
コードで使用される、setUserIdIntoUserDataHashtable()
ヘルパー・メソッドを示しています。このメソッドでは、文字列定数CURRENT_USER_ID
で指定されるキーを使用して、この数値ユーザーIDをユーザー・データ・ハッシュ表に格納します。
// In the application module private void setUserIdIntoUserDataHashtable() { Integer userid = getUserIdForLoggedInUser(); Hashtable userdata = getDBTransaction().getSession().getUserData(); userdata.put(CURRENT_USER_ID, userid); }
この例の対応するエンティティ・オブジェクトでは、次の例のようなヘルパー・メソッドを使用して、この数値ユーザーIDを参照するcreate()
メソッドをオーバーライドできます。オーバーライドされたメソッドでは、CreatedBy
属性を現在認証されているユーザーの数値ユーザーIDの値にプログラム的に設定します。
protected Number getCurrentUserId() { Hashtable userdata = getDBTransaction().getSession().getUserData(); Integer userId = (Integer)userdata.get(CURRENT_USER_ID); return userdata != null ? Utils.intToNumber(userId):null; }
// 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(); }
現在のユーザー・セッション情報へのアクセスにGroovyを使用する方法
フレームワークでGroovyスクリプトを使用できる、オブジェクトにアクセス可能なadf
という名前のトップレベルのオブジェクトが用意されています。adf.userSession
オブジェクトは、ADFビジネス・コンポーネント・ユーザー・セッションへの参照を返します。これを使用してセッションに含まれているuserData
ハッシュ・マップ内の値を参照できます。
次の例は、MyKey
という名前のuserData
ハッシュ・マップ・キーの参照に使用するGroovyスクリプトを示しています。
adf.userSession.userData.MyKey
現在の日時へのアクセス
ADFビジネス・コンポーネントには、現在の日時を取得するためにエンティティ・オブジェクトのビジネス・ロジックで使用できる事前定義されたGroovy式が組み込まれています。
エンティティ・オブジェクトのビジネス・ロジックにおいては、現在の日時の参照が役立つ場合があります。次のGroovyスクリプト式を使用すると、現在の日付または現在の日付と時間を参照できます。
-
adf.currentDate
- 現在の日付(時間を切捨て)を戻します。 -
adf.currentDateTime
- 現在の日付と時間を戻します。
エンティティ・オブジェクトのビジネス・ロジックでのGroovyスクリプトの使用方法の詳細は、「ビジネス・コンポーネントでのGroovyスクリプト言語の使用」を参照してください。
正常に完了したコミットに関する通知の送信
保留中の行変更が正常にコミットされると、このようなADFビジネス・コンポーネントのエンティティ行に対してafterCommit()
メソッドがコールされます。このメソッドを使用して通知を送信することもできます。
変更保留中のリストに含まれていた各エンティティ行に対してafterCommit()
メソッドがコールされ、データベースに正しく格納されます。このメソッドを使用して、コミットに関する通知を送信できます。
コミット成功時に通知を送信するもっとよい方法は、ビジネス・イベントを宣言する方法です。ビジネス・イベントの作成方法は、「ビジネス・イベントの作成」を参照してください。
エンティティ行削除の条件付き禁止
remove()
メソッドを使用してADFエンティティ行の削除を制御します。
remove()
メソッドは、エンティティ行削除の前に、その行に対して起動します。remove()
メソッドでは、JboException
をスローし、該当する条件を満たさない場合は行の削除を回避するよう設定できます。
たとえば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()
メソッドをオーバーライドして、実行時に特定のADFエンティティ・オブジェクト属性がupdateable
かどうかをチェックします。
エンティティ・オブジェクト・クラス内のisAttributeUpdateable()
メソッドをオーバーライドすると、特定の属性が更新可能かどうかを、実行時に適切な条件に従ってプログラム的に判断できます。
次の例は、現在の認証されたユーザーがスタッフ・メンバーである場合にのみPersonTypeCode
属性を更新可能にするため、エンティティ・オブジェクトによりisAttributeUpdateable()
がどのようにオーバーライドされるかを示しています。エンティティ・オブジェクトでは、このメソッドを実行する際、更新可能性の確認対象となる整数属性索引を渡します。
特定の属性に対する条件付きの更新可能性ロジックは、属性索引に基づいて、if
文またはswitch
文内に実装できます。ここでは、PERSONTYPECODE
がエンティティ・オブジェクトのカスタムJavaクラスに保持されている整数属性の索引列挙を参照します。
ノート:
エンティティ・ベースのビュー・オブジェクトは、エンティティ・オブジェクト内でカプセル化された他の要素と同様に、この条件付きの更新可能性も継承します。この種の条件付き更新可能性ロジックを、一時ビュー・オブジェクト属性に固有の方法で実装する必要がある場合、または、ビュー・オブジェクト内の複数のエンティティ・オブジェクトのデータを含む一部の条件を適用する場合、ビュー・オブジェクトのビュー行クラス内の同じメソッドをオーバーライドすると、目的の結果が得られます。
// 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ビジネス・コンポーネントを使用すると、共通の検証コードを取得して組込みの宣言的検証規則とともに使用するカスタム検証規則クラスを作成できます。
ADFビジネス・コンポーネントには、開発者が使用できる組込みの宣言的検証規則の基本セットが付属しています。ただし、エンティティ・オブジェクト用のバリデータ・アーキテクチャの強力な機能は、独自のカスタム検証規則を作成できることです。同じ種類の検証コードを繰り返し作成している場合は、カスタム検証規則クラスを作成し、このような共通の検証パターンをパラメータ化された方法で取得できます。
定義したカスタム検証規則クラスは、JDeveloperに登録し、組込み規則と同じように簡単に使用できます。実際、カスタム検証規則をカスタムUIパネルにバンドルすることもできます。JDeveloperはこれを利用して、検証規則で必要とするパラメータを開発者が簡単に使用および構成できるようにします。
カスタム検証規則の作成方法
エンティティ・オブジェクト用のカスタム検証規則を作成するには、oracle.jbo.rules
パッケージのJboValidatorInterface
を実装するJavaクラスが必要です。「新規ギャラリ」からスケルトン・クラスを作成できます。
始める前に:
カスタム検証規則に関する知識があると役立つ場合があります。詳細は、「カスタム検証規則の実装」を参照してください。
また、他の検証機能を使用して追加できる機能についても理解しておくと役立ちます。詳細は、「プログラム的なビジネス・ルールの追加機能」を参照してください。
カスタム・バリデータを作成するには:
-
「アプリケーション」ウィンドウで、バリデータを作成するプロジェクトを右クリックし、「新規」→「ギャラリから」を選択します。
-
「新規ギャラリ」で、「ビジネス層」を開き、「ADFビジネス・コンポーネント」を選択してから、「検証ルール」を選択し、「OK」をクリックします。
-
「検証ルール・クラスの作成」ダイアログで、ルールの名前とパッケージを入力します。
-
表示名と説明を入力して、「OK」をクリックします。
次の例に示すように、JBOValidatorInterface
には、1つのメインvalidate()
メソッドと、Description
プロパティ用のgetterおよびsetterメソッドが含まれます。
package oracle.jbo.rules; public interface JboValidatorInterface { void validate(JboValidatorContext valCtx) { } java.lang.String getDescription() { } void setDescription(String description) { } }
検証規則の動作がパラメータ化されて柔軟性が増してから、検証クラスの各パラメータにBeanプロパティを追加します。たとえば、次の例のコードによりDateMustComeAfterRule
という名前のカスタム検証ルールが実装され、これによりある日付属性が別の日付属性より後でなければならないことを検証します。規則を使用する開発者が検証対象の2つの日付として使用する日付属性の名前を構成できるように、このクラスではinitialDateAttrName
とlaterDateAttrName
という2つのプロパティが定義されています。
次の例は、カスタム検証ルールを実装するコードを示しています。これは、AbstractValidator
を拡張し、エンティティ・オブジェクトのカスタム・メッセージ・バンドルの使用を継承しています。開発者がエンティティ・オブジェクトの規則を使用すると、JDeveloperは検証エラー・メッセージをここに保存します。
検証規則のvalidate()
メソッドは、実行時に規則クラスが機能を実行する必要がある場合に常に呼び出されます。このコードが実行する基本的なステップは次のとおりです。
- エンティティ・レベルでバリデータが正しくアタッチされていることを確認します。
- 検証するエンティティ行を取得します。
- 先の日と後の日の属性の値を取得します。
- 先の日が後の日より前であることを検証します。
- 検証が失敗した場合は例外をスローします。
カスタム検証規則を簡単に再利用できるように、通常は、規則を利用するアプリケーションでの参照用として規則をJARファイルにパッケージします。
// 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; }
JDeveloperでのカスタム規則の登録および使用方法
カスタム検証規則は作成後、JDeveloper IDEのプロジェクトまたはアプリケーションレベルに追加できるため、他の開発者がその規則を宣言的に使用できます。
プロジェクト・レベルでのカスタム・バリデータの登録
カスタム検証規則をプロジェクト・レベルで登録した場合は、プロジェクト内でその検証規則を使用できます。
始める前に:
カスタム検証規則に関する知識があると役立つ場合があります。詳細は、「カスタム検証規則の実装」を参照してください。
また、他の検証機能を使用して追加できる機能についても理解しておくと役立ちます。詳細は、「プログラム的なビジネス・ルールの追加機能」を参照してください。
カスタム検証ルールを登録するには、「カスタム検証規則の作成方法」で説明しているように、先にその検証ルールを作成しておく必要があります。
エンティティ・オブジェクトを含むプロジェクトでカスタム検証規則を登録するには:
- 「アプリケーション」ウィンドウで、エンティティ・オブジェクトを含むプロジェクトを右クリックし、「プロジェクト・プロパティ」を選択します。
- 「プロジェクト・プロパティ」ダイアログで、「ADFビジネス・コンポーネント」を開き、「登録済ルール」を選択します。
- 「登録済の規則」ページで、「追加」をクリックします。
- 「検証ルールの登録」ダイアログで、作成した検証ルールを参照して検索し、「OK」をクリックします。
IDEレベルでのカスタム・バリデータの登録
カスタム検証規則をJDeveloperのIDEレベルで登録すると、現在のプロジェクトのみでなく、他のプロジェクトでもその検証規則を使用できます。
始める前に:
カスタム検証規則に関する知識があると役立つ場合があります。詳細は、「カスタム検証規則の実装」を参照してください。
また、他の検証機能を使用して追加できる機能についても理解しておくと役立ちます。詳細は、「プログラム的なビジネス・ルールの追加機能」を参照してください。
カスタム検証ルールを登録するには、「カスタム検証規則の作成方法」で説明しているように、先にその検証ルールを作成しておく必要があります。
IDEレベルのカスタム・バリデータを登録するには: