ヘッダーをスキップ
Oracle Application Development Framework Forms/4GL開発者のための開発者ガイド
10g(10.1.3.0)
B40013-02
  目次
目次
索引
索引

戻る
戻る
 
次へ
次へ
 

9 エンティティ・オブジェクト内のプログラム的なビジネス・ルールの実装

この章では、最も一般的なビジネス・ルールの実装に関する、主要なエンティティ・オブジェクトのイベントと機能について説明します。

この章の内容は次のとおりです。

9.1 プログラム的なビジネス・ルールの概要

組込みの宣言的な検証機能を補うエンティティ・オブジェクトには、Javaコードを使用してカプセル化されたビジネス・ロジックの実装を処理する、Method Validatorやいくつかのイベントが含まれています。この章の終わりには、図9-1に示すすべての概念に加えて、他の内容も理解できるようになります。

図9-1 プログラム的なビジネス・ロジックにおける主要なエンティティ・オブジェクトの機能とイベント

この図は、エンティティ・オブジェクト機能を示しています。

9.2 検証サイクルの理解

それぞれのエンティティ行は、データが有効であるかどうかを追跡します。既存のエンティティ行をデータベースから取得すると、エンティティは有効であると判断されます。既存のエンティティ行の最初の持続属性が変更された場合、または、新しいエンティティ行が作成された場合、エンティティは無効であるとマークされます。

また、構成される側の子エンティティ行は、それを構成する親エンティティ・オブジェクトに不可欠であるとみなされるため、構成される側の子エンティティ行になんらかの変更を加えると、親エンティティは無効であるとマークされます。

エンティティが無効な状態にある場合、エンティティを有効にする前に、構成した宣言的な検証と、実装したプログラム的な検証規則が再度評価されます。あるエンティティ行が実行時に有効であるかどうかは、その行でisValid()メソッドをコールして確認できます。

9.2.1 エンティティ・オブジェクト検証規則のタイプ

エンティティ・オブジェクト検証規則は、属性レベルおよびエンティティ・レベルのいずれかの基本カテゴリに分類できます。

9.2.1.1 属性レベルの検証規則

特定のエンティティ・オブジェクト属性に対する属性レベルの検証は、エンド・ユーザーまたはプログラム・コードによる属性値の変更が試行されるとトリガーされます。属性を設定する順序は指定できないため、属性レベルの検証規則は、規則の成否がその属性の候補値のみに依存する場合にのみ使用する必要があります。

属性レベルの検証の例を次に示します。

  • サービス・リクエストのAssignedDateの値には、過去の日付は設定できません。

  • サービス・リクエストのProdId属性は、既存の製品である必要があります。

9.2.1.2 エンティティ・レベルの検証規則

その他のすべての検証規則は、エンティティ・レベルの検証規則となります。これらの規則には、実装の際、規則の成否の決定に2つ以上のエンティティ属性、または、構成される子エンティティ行を考慮する必要があります。

属性レベルの検証の例を次に示します。

  • サービス・リクエストのAssignedDateの値には、RequestDate以降の日付を設定する必要があります。

  • サービス・リクエストのProdId属性は、既存の製品である必要があります。

エンティティ・レベルの検証規則は、Rowに対しvalidate()メソッドをコールするとトリガーされます。次の場合にトリガーされます。

  • エンティティ・オブジェクトにおいてメソッドを明示的にコールした場合

  • 無効なエンティティ行を含むビュー行においてメソッドを明示的にコールした場合

  • ビュー・オブジェクトのイテレータにより、現在の行の変更を許可する前に、ビュー・オブジェクトの現在の行に対してメソッドがコールされた場合

  • トランザクションのコミット処理により、データベースへ変更を送信する前に、保留中の変更リスト内の無効なエンティティが有効にされた場合

9.2.2 コミット処理と検証の理解

トランザクションのコミット処理は、3つの基本的なフェーズに分かれています。

  1. 保留中の変更リスト内のすべての無効エンティティ行が有効であることを確認します。

  2. 適切なDML操作を実行し、保留中の変更をデータベースに送信します。

  3. トランザクションをコミットします。

エンティティ・オブジェクト内に、SELECT文内の送信済の変更の参照に応じて問合せまたはストアド・プロシージャを実行するようなビジネス検証ロジックがある場合、9.6.3項「指定された型のすべてのエンティティに関連する条件の検証」に示すbeforeCommit()メソッドを使用してコーディングする必要があります。このメソッドは、すべてのDMLが適用された後に実行されるため、そのメソッドが実行した問合せやストアド・プロシージャでは、保存された、コミット前の保留中の変更をすべて参照できます。


注意:

検証前の変更をコミットせず、トランザクションから強制的に送信するようなトランザクション・レベルのpostChanges()メソッドは、同一のHTTPリクエスト内でのトランザクションのコミットまたはロールバックが必ず実行される場合を除き、Webアプリケーションでの使用はお薦めしません。使用した場合、複数の異なるクライアントにより、アプリケーション・モジュールとデータベース接続の両方がプールされ、シリアルに共有されるという事態が環境内で発生する可能性があります。

9.2.3 無限検証サイクルの回避

現在のエンティティや他のエンティティの属性を更新するコードが検証規則に含まれている場合、エンティティの検証により、対象のエンティティやその他のエンティティが無効になる場合があります。保留中の変更リスト内のすべての無効なエンティティを検証するトランザクションのコミット処理フェーズの一環として、このトランザクションは、保留中のすべてのエンティティ行が有効になるまで、1回につき保留中の変更リストを最大10回までパスします。

10回パスした後に、リスト内に無効なエンティティが残っている場合、次の例外が発生します。

JBO-28200: Validation threshold limit reached. Invalid Entities still in cache

これは、意図しないエンティティの無効化を繰り返すような処理を回避するため、検証規則コードをデバッグする必要があることを示しています。

9.2.4 検証が失敗したときに行われる処理

エンティティ・オブジェクトの検証規則で例外がスローされた場合、その例外はバンドルされ、クライアントに戻されます。トランザクションのpostChanges処理中、イベントを処理するためオーバーライドしたメソッドによって検証の失敗がスローされる場合、現在のpostChangesサイクルですでに実行されている可能性のあるデータベースのINSERTUPDATEおよびDELETE文がロールバックされます。

9.2.5 エンティティ・オブジェクト行の状態の理解

エンティティ行がメモリー内に存在する場合、エンティティの状態には行の論理状態が反映されています。図9-2は、様々なエンティティ行の状態と、エンティティ行の状態がどのように遷移するかを示しています。エンティティ行が最初に作成されると、その状態はNewになります。setNewRowState()メソッドを使用して、エンティティがInitializedであるとマークできます。これにより、ユーザーが属性を1つ以上設定し、状態がNewに戻るまで、トランザクションの保留中の変更リストから削除されます。Unmodified状態とは、データベースから取得されてから、まだ変更されていないエンティティの状態を表します。また、NewまたはModified状態のエンティティがトランザクションのコミット完了後に遷移する状態でもあります。トランザクション内で削除のために保留中の場合は、Unmodifiedエンティティ行はDeleted状態へと遷移します。最終的に、New状態の行がトランザクションのコミット前に削除されたり、Unmodified状態の行が正常に削除された場合、その行はDead状態へと遷移します。

図9-2 エンティティ行の状態と遷移のダイアグラム

エンティティ行の状態と遷移の図

getEntityState()メソッドを使用すると、ビジネス・ロジック・コードのエンティティ行の現在の状態にアクセスできます。


注意:

トランザクションをコミットせずに保留中の変更を送信するためpostChanges()メソッドを使用した場合、getPostState()メソッドは、データベースに送信されたかどうかに基づいてエンティティ状態を戻します。たとえば、プログラム的なpostChanges()メソッド・コールにより新しいエンティティ行がデータベースに挿入されたものの、まだコミットされていない場合、getPostState()getEntityState()では値が異なります。新規行が送信されたため、getPostState()メソッドは、Unmodified状態を反映します。ただし、getEntityState()ではまだエンティティが現在のトランザクションにおいてNewであると認識します。

9.3 Method Validatorの使用

Method Validatorは、独自のJavaコードを使用した宣言的な検証規則を補完するものとして、オラクル社がお薦めする主要な手段です。Method Validatorでは、独自の検証メソッド内に記述したJavaコードが、エンティティ・オブジェクト検証サイクルにおいて適切なタイミングでトリガーされます。コード内でそれぞれ別のメソッド名をトリガーする場合、属性レベルのMethod Validatorとエンティティ・レベルのMethod Validatorはいくつでも追加できます。すべての検証メソッド名は、validateから始まる必要があります。ただし、その規則に従っているかぎりは、その機能に最適な名前を自由に付けることができます。

9.3.1 属性レベルのメソッド検証の作成方法

属性レベルのMethod Validatorの作成方法:

  1. エンティティ・オブジェクト・エディタを開きます。

  2. エンティティ・オブジェクトにまだカスタムJavaクラスがない場合、最初に「Java」ページを開き、「エンティティ・オブジェクト・クラス」の生成を有効にし、「適用」をクリックして*.javaファイルを生成します。

  3. 「検証」ページを開き、検証する属性を選択します。

  4. 「新規」をクリックし、検証規則を追加します。

  5. 図9-3に示すように、「規則」ドロップダウン・リストから「Method Validator」タイプを選択します。

「検証規則の追加」ダイアログには、属性レベル検証メソッドに対応するメソッド・シグネチャが表示されます。次のいずれかを選択できます。

  • エンティティ・オブジェクトのカスタムJavaクラスに、適切なシグネチャのあるメソッドがすでに存在する場合、リストに表示されます。これは「メソッドの作成と選択」チェック・ボックスの選択を解除した後に選択できます。

  • 「メソッドの作成と選択」を選択したままにすると、validateから始まるどのようなメソッド名でも「メソッド名」ボックスに入力できます。「OK」をクリックすると、適切なシグネチャとともに、メソッドがエンティティ・オブジェクトのカスタムJavaクラスに追加されます。

最後に、この検証規則が失敗した場合にエンド・ユーザーに表示される、デフォルト・ロケールのエラー・メッセージのテキストを追加します。

図9-3 属性レベルのMethod Validatorの追加

「検証規則の追加」ダイアログの図

9.3.2 属性レベルのMethod Validatorを作成するときに行われる処理

Method Validatorを新たに追加すると、JDeveloperでは新しい検証規則を反映するように、XMLコンポーネント定義が更新されます。メソッドの作成を指定した場合、そのメソッドはエンティティ・オブジェクトのカスタムJavaクラスに追加されます。例9-1は、サービス・リクエストのAssignedDateが現在月の日付であることを確認する、簡単な属性レベル検証規則を示しています。メソッドには、対応する属性と同じ型の引数が与えられ、その条件付きのロジックはこの入力パラメータの値に基づいています。属性バリデータの実行時には、属性値はまだ該当する新しい値に設定されていません。そのため、AssignedDate属性の属性バリデータ内でgetAssignedDate()メソッドをコールすると、クライアントが設定しようとしている候補値ではなく、現在の値が戻されます。

例9-1 簡単な属性レベルのMethod Validator

// In ServiceRequestImpl.java in SRDemo Sample
public boolean validateAssignedDate(Date  data) {
  if (data != null && data.compareTo(getFirstDayOfCurrentMonth()) <= 0) {
    return false;
  }
  return true;
}

注意:

compareTo()メソッドの戻り値は、2つの日付が等しい場合はゼロ(0)、最初の日付が次の日付より前の場合はマイナス1(-1)、最初の日付が次の日付より後の場合はプラス1(1)となります。

9.3.3 エンティティ・レベルのMethod Validatorの作成方法

エンティティ・レベルのMethod Validatorの作成方法:

  1. エンティティ・オブジェクト・エディタを開きます。

  2. エンティティ・オブジェクトにまだカスタムJavaクラスがない場合、最初に「Java」ページを開き、「エンティティ・オブジェクト・クラス」の生成を有効にし、「適用」をクリックして*.javaファイルを生成します。

  3. 「検証」ページを開き、エンティティ・オブジェクト自体を表すルート・ノードをツリーから選択します。

  4. 「新規」をクリックし、検証規則を追加します。

  5. 図9-4に示すように、「規則」ドロップダウン・リストから「Method Validator」タイプを選択します。

「検証規則の追加」ダイアログには、エンティティ・レベル検証メソッドに対応するメソッド・シグネチャが表示されます。次のいずれかを選択できます。

  • エンティティ・オブジェクトのカスタムJavaクラスに、適切なシグネチャのあるメソッドがすでに存在する場合、リストに表示されます。これは「メソッドの作成と選択」チェック・ボックスの選択を解除した後に選択できます。

  • 「メソッドの作成と選択」を選択したままにすると、validateから始まるどのようなメソッド名でも「メソッド名」ボックスに入力できます。「OK」をクリックすると、適切なシグネチャとともに、メソッドがエンティティ・オブジェクトのカスタムJavaクラスに追加されます。

最後に、この検証規則が失敗した場合にエンド・ユーザーに表示される、デフォルト・ロケールのエラー・メッセージのテキストを追加します。

図9-4 エンティティ・レベルのMethod Validatorの追加

「検証規則の追加」ダイアログの図

9.3.4 エンティティ・レベルのMethod Validatorを作成するときに行われる処理

Method Validatorを新たに追加すると、JDeveloperでは新しい検証規則を反映するように、XMLコンポーネント定義が更新されます。メソッドの作成を指定した場合、そのメソッドはエンティティ・オブジェクトのカスタムJavaクラスに追加されます。例9-2は、サービス・リクエストのAssignedDateRequestDateより後の日付であることを確認する、簡単なエンティティ・レベル検証規則を示しています。

例9-2 簡単なエンティティ・レベルのMethod Validator

public boolean validateAssignedDateAfterRequestDate() {
  Date assignedDate = getAssignedDate();
  Date requestDate  = getRequestDate();
  if (assignedDate != null && assignedDate.compareTo(requestDate) < 0) {
    return false;
  }
  return true;
}

9.3.5 検証規則エラー・メッセージの翻訳について

エンティティ・オブジェクト属性のロケール固有のUIコントロールのヒントと同様に、検証規則のエラー・メッセージは、エンティティ・オブジェクトのコンポーネント・メッセージ・バンドル・ファイルに追加されます。これはアプリケーションのデフォルトのロケールの文字列を表します。エラー・メッセージの翻訳バージョンについては、前の章で説明したUIコントロール・ヒントの翻訳と同様の手順で追加してください。

9.3.6 属性レベルの検証のエラー・メッセージにおける、無効な値の参照について

属性レベルの検証規則を追加する際に指定する検証エラー・メッセージは、文字列内のメッセージ・パラメータ・トークン「{3}」を参照することにより、無効な値を参照する場合があります。指定されている他のエラー・パラメータは、ValidationExceptionのプログラムによる処理に使用できますが、通常はメッセージ文字列自体では使用できません。

9.4 プログラム的な導出属性値の割当て

宣言的なデフォルト設定では不十分なとき、次の場合にエンティティ・オブジェクトでプログラムによるデフォルト設定を実行できます。

9.4.1 新しい行に対する作成時のデフォルト値

create()メソッドは、エンティティ行の最初の作成時にデフォルト値の初期化を処理するエンティティ・オブジェクト・イベントを提供します。例9-3は、SRDemoアプリケーションのServiceHistoryエンティティ・オブジェクトのオーバーライドされたcreateメソッドです。このメソッドは、新しいサービス履歴エンティティ行のSvhType属性、CreatedBy属性、およびLineNo属性に値を移入する、属性のsetterメソッドをコールします。

例9-3 新しい行の属性値に対する、プログラムによるデフォルト設定

// In ServiceHistoryImpl.java in SRDemo sample
protected void create(AttributeList nameValuePair) {
  super.create(nameValuePair);
  setSvhType(getDefaultNoteType());
  setCreatedBy(getCurrentUserId());
  setLineNo(new Number(getServiceRequest().getMaxHistoryLineNumber()+1));
}

9.4.1.1 create()メソッドとinitDefaults()メソッドのいずれかの選択

エンティティ行がNew状態であり、そのエンティティ行に対してrefresh()メソッドをコールした場合、REFRESH_REMOVE_NEW_ROWSまたはREFRESH_FORGET_NEW_ROWSフラグを指定しないと、エンティティ行はInitialized状態に戻ります。このプロセスの一環として、エンティティ・オブジェクトのinitDefaults()メソッドが実行されますが、create()メソッドは再度実行されません。そのため、行が最初に作成されたとき、また、initialized状態にリフレッシュされたときに実行するプログラム的なデフォルト設定ロジックで、initDefaults()メソッドをオーバーライドします。

9.4.1.2 データベース順序からの属性値のデフォルト設定

6.6.3.7項「トリガーによって割り当てられた値の同期化」では、コミット時にデータベース順序によって移入する必要のある主キー属性の値のあるDBSequence型の使用方法について説明しました。エンティティ行の作成時にユーザーが値を参照でき、この値がデータ保存時に変更されないように、順序番号を事前に割り当てる必要が生じることがあります。そのためには、例9-4に示すように、オーバーライドされたcreate()メソッドでoracle.jbo.serverパッケージのSequenceImplヘルパー・クラスを使用します。この例は、SRDemoアプリケーションのProductエンティティ・オブジェクトのカスタムJavaクラスのコードを示しています。super.create()をコールした後に、順序名と現在のトランザクション・オブジェクトを渡し、SequenceImplオブジェクトの新規インスタンスを作成します。続いてSequenceImplgetSequenceNumber()メソッドからの戻り値を含む、setProdId()属性のsetterメソッドをコールします。

例9-4 作成時に順序からの属性値のデフォルト設定

// In ProductImpl.java
import oracle.jbo.server.SequenceImpl;
// Default ProdId value from PRODUCTS_SEQ sequence at entity row create time
protected void create(AttributeList nameValuePair) {
  super.create(nameValuePair);
  SequenceImpl sequence = new SequenceImpl("PRODUCTS_SEQ",getDBTransaction());
  setProdId(sequence.getSequenceNumber());
}

9.4.2 保存前の導出値の割当て

行を保存する前に、プログラムによるデフォルト値をエンティティ・オブジェクトの属性値に割り当てる場合、prepareForDML()メソッドをオーバーライドし、適切な属性のsetterメソッドをコールし、導出される属性値を移入します。INSERTUPDATEまたはDELETEの処理中にのみ割当てを実行するには、DML_INSERTDML_UPDATEDML_DELETEのそれぞれの整数値の定数と、このメソッドに渡されたoperationパラメータの値を比較できます。

例9-5は、特定の型のサービス履歴メモが作成されたときのサービス・リクエストのステータスを自動変更するため、SRDemoアプリケーション内のServiceHistoryエンティティ・オブジェクトで使用される、オーバーライドされたprepareForDML()メソッドを示しています。新しいサービス履歴のエントリを挿入すると、このコードにより次のステータスが変更されます。

  • 顧客が新規履歴メモを追加した場合の、保留中または処理済のサービス・リクエストのステータスの未処理への変更

  • 技術者が新規履歴メモを追加した場合の、未処理のサービス・リクエストのステータスの保留中への変更

例9-5 PrepareForDMLを使用した保存前の導出値の割当て

// In ServiceHistoryImpl.java
protected void prepareForDML(int operation, TransactionEvent e) {
  super.prepareForDML(operation, e);
  // If we are inserting a new service history entry...
  if (operation == DML_INSERT) {
    ServiceRequestImpl serviceReq = getServiceRequest();
    String historyType = getSvhType();
    // If request is pending or closed and customer adds note, status => Open
    if ((serviceReq.isPending() || serviceReq.isClosed())
        && CUSTOMER_TYPE.equals(historyType)) {
        serviceReq.setOpen();
    }
    // If request is open & technician adds a non-hidden note, status => Pending
    if (serviceReq.isOpen() && TECHNICIAN_TYPE.equals(historyType)) {
        serviceReq.setPending();
    }
  }
}

9.4.3 属性値が設定されている場合の、導出値の割当て

他の属性値が設定されているときに、導出された属性値を割り当てるには、最初の属性のsetterメソッドにコードを追加します。例9-6は、SRDemoアプリケーションのServiceRequestエンティティ・オブジェクトのAssignedTo属性に対するsetterメソッドを示しています。setAttributeInternal()をコールしてAssignedTo属性の値を設定すると、AssignedDate属性のsetterメソッドにより、現在日時の値が設定されます。

例9-6 AssignedTo属性の変更に応じて割当て日を設定するメソッド

// In ServiceRequestImpl.java
public void setAssignedTo(Number value) {
  setAttributeInternal(ASSIGNEDTO, value);
  setAssignedDate(getCurrentDateWithTime());
}

注意:

ここで説明した生成済の属性のgetterメソッドとsetterメソッドへのカスタム・コードの追加は、安全に行うことができます。JDeveloperでクラスのコードが変更された場合でも、カスタム・コードはそのまま保持されます。

9.5 refreshメソッドを使用した、エンティティに対する保留中の変更の取消し

refresh(int flag)メソッドをRowに対して使用すると、保留中の変更をリフレッシュできます。refresh()メソッドの動作は、パラメータとして渡すフラグによって決まります。この動作を制御する3つの主要なフラグの値は、Rowインタフェースの次の定数です。

9.5.1 リフレッシュ中の新規行の動作の制御

デフォルトでは、refresh()を実行したNew状態のすべてのエンティティ行がInitialized状態の空白行に戻ります。宣言的なデフォルト値と、initDefaults()メソッドに記述されたプログラム的なデフォルトはリセットされますが、エンティティ・オブジェクトのcreate()メソッドは、この無効化のプロセスでは実行されません。

このデフォルトの動作は、次の2つのフラグのうちの1つと、前述の項のフラグの1つをビット単位のOR演算子によって組み合せて変更できます。

  • REFRESH_REMOVE_NEW_ROWS: リフレッシュ時に新しい行が削除されます。

  • REFRESH_FORGET_NEW_ROWS: 新しい行はDeadとマークされます。

9.5.2 構成される側の子エンティティ行へのリフレッシュのカスケード

REFRESH_CONTAINEESフラグと前述の有効なフラグの組合せをビット単位のORで指定して、構成される側の子エンティティ行をrefresh()操作でカスケードできます。このため、エンティティにより、構成される側の子エンティティに対し、同じモードでrefresh()が実行されます。

9.6 検証でのビュー・オブジェクトの使用

ビジネス・ロジックでSQL問合せの実行が必要とされる場合、一般的には、そのタスクの実行にはビュー・オブジェクトを使用します。検証のために実行するSQL文では、エンティティ・ベースのビュー・オブジェクトであれば、エンティティ・キャッシュ内の保留中の変更が参照されます。読取り専用ビュー・オブジェクトでは、データベースに送信されたデータのみを取得します。

9.6.1 実行時の検証用ビュー・オブジェクトの作成

エンティティ・オブジェクトは、すべてのアプリケーション・シナリオ内での再使用を考慮して設計されているため、特定のアプリケーション・モジュールのデータ・モデルにおけるビュー・オブジェクト・インスタンスに直接的には依存できません。依存する場合、他のアプリケーション・モジュールで再使用ができないという事態が発生します。

そのかわり、エンティティ・オブジェクトから実行時に検出されるルート・アプリケーション・モジュールへアクセスし、そのアプリケーション・モジュール・インスタンスのcreateViewObject()メソッドによって、必要な検証ビュー・オブジェクト・インスタンスを作成できます。このAPIでは、設計時にデータ・モデルに追加されたビュー・オブジェクト・インスタンスと同様に、ビュー・オブジェクトにインスタンス名を割り当てることができます。そのため、必要に応じてfindViewObject()を使用して再検索できます。

SQLベースの検証コードは何度も実行できるため、毎回、使用するたびにビュー・オブジェクト・インスタンスを作成し、使用後に削除するという方法は効率的ではありません。そのかわり、既存のビュー・オブジェクト・インスタンスを使用するか、既存のインスタンスが存在しない場合は、最初に必要なときに作成するよう、例9-7に示すようなヘルパー・メソッドを実装できます。実行時に作成されたビュー・オブジェクト・インスタンスのインスタンス名がデータ・モデル内で設計時に指定したインスタンスの名前と競合しないよう、最初にValidation_などの文字列を付けるなど、ビュー・オブジェクト定義名に基づく命名規則を採用します。これは1つの方法にすぎません。設計時の名前と競合しないのであれば、どのような命名規則でも採用できます。

例9-7 検証用にビュー・オブジェクトにアクセスするヘルパー・メソッド

/**
 * Find instance of view object used for validation purposes in the
 * root application module. By convention, the instance of the view
 * object will be named Validation_your_pkg_YourViewObject.
 *
 * If not found, create it for the first time.
 *
 * @return ViewObject to use for validation purposes
 * @param viewObjectDefName
 */
protected ViewObject getValidationVO(String viewObjectDefName) {
  // Create a new name for the VO instance being used for validation
  String name = "Validation_" + viewObjectDefName.replace('.', '_');
  // Try to see if an instance of this name already exists
  ViewObject vo = getDBTransaction().getRootApplicationModule()
                                    .findViewObject(name);
  // If it doesn't already exist, create it using the definition name
  if (vo == null) {
    vo = getDBTransaction().getRootApplicationModule()
                           .createViewObject(name,viewObjectDefName);
  }
  return vo;
}

このようなヘルパー・メソッドが存在するため、検証コードからgetValidationVO()をコールし、使用するビュー・オブジェクト定義の完全修飾名を渡すことができます。これにより、例9-8のようなコードを記述できます。

例9-8 Method Validatorでの検証ビュー・オブジェクトの使用

// Sample entity-level validation method
public boolean validateSomethingUsingViewObject() {
  Number numVal = getSomeEntityAttr();
  String stringVal = getAnotherEntityAttr();
  // Get the view object instance for validation
  ViewObject vo = getValidationVO("devguide.example.SomeViewObjectName");
  // Set it's bind variables (which it will typically have!)
  vo.setNamedBindWhereClauseParam("BindVarOne",numVal);
  vo.setNamedBindWhereClauseParam("BindVarTwo",stringVal);
  vo.executeQuery();
  if ( /* some condition */) {
    /*
     * code here returns true if the validation succeeds
     */
  }
  return false;
}

サンプル・コードで示すように、検証に使用されるビュー・オブジェクトには通常、1つ以上の名前付きのバインド変数が含まれます。ビュー・オブジェクトが取得するデータの種類によって、前述の/* some condition */式は異なります。たとえば、ビュー・オブジェクトのSQL問合せがCOUNT()などの集約関数を選択した場合、条件では一般的にvo.first()メソッドを使用して最初の行にアクセスし、続いてgetAttribute()メソッドを使用して属性値にアクセスし、そのカウントに対してデータベースが戻す結果を参照します。

問合せから0行または1行のどちらが戻されたかによって、検証が成功または失敗した場合、この条件はvo.first()nullを戻したかどうかのみを検証します。vo.first()nullを戻した場合、最初の行は存在しません。つまり、問合せで行は検出されていません。

また、ビュー・オブジェクトから取得された1つ以上の問合せ結果に対し、検証の成否を繰り返し確認することもあります。

9.6.2 効率的な存在チェックの実装

一般的なSQLベースの検証の1つに、関連表内に候補となる外部キーが存在するかどうかを確認する簡単なテストがあります。このタイプの検証は、エンティティ定義でfindByPrimaryKey()メソッドを使用すると実装できますが、エンティティが存在する場合、エンティティのすべての属性を取得します。他により負荷の低い存在チェックを行う方法として、検証にビュー・オブジェクトを使用する方法があります。

例9-9は、SRDemoアプリケーションのProductエンティティ・オブジェクトのカスタム・エンティティ定義クラスに含まれるexists()メソッドを示しています。最初に、このメソッドでは、パラメータとして、DBTransactionを受け入れる前述のヘルパー・メソッドのバリアントを使用し、該当する検証ビュー・オブジェクトのインスタンスを戻します。これは、同じクラスのgetProductExistsVO()メソッド内にカプセル化されます。

検証に使用される読取り専用ビュー・オブジェクトには、oracle.srdemo.model.entities.validationqueriesパッケージ内でProductExistsByIdという名前が付いています。このビュー・オブジェクトにはカスタムJavaクラス(ProductExistsByIdImpl)があるため、exists()メソッドのコードでは、強く型付けされたsetTheProductId()メソッドを使用し、ビュー・オブジェクトが定義する名前付きのバインド変数を設定できます。続いて、コードが問合せを実行し、行が検出されたかどうかに基づき、ブール変数foundItを設定します。

例9-9 ビュー・オブジェクトとエンティティ・キャッシュを使用した、効率的な存在チェック

// In ProductDefImpl.java in SRDemo sample
public boolean exists(DBTransaction t, Number productId) {
  boolean foundIt = false;
  ProductExistsByIdImpl vo = getProductExistsVO(t);
  vo.setTheProductId(productId);
  vo.setForwardOnly(true);
  vo.executeQuery();
  foundIt = (vo.first() != null);
  /*
   * If we didn't find it in the database,
   * try checking against any new employees in cache
   */
  if (!foundIt) {
    Iterator iter = getAllEntityInstancesIterator(t);
    while (iter.hasNext()) {
      ProductImpl product = (ProductImpl)iter.next();
      /*
       * NOTE: If you allow your primary key attribute to be modifiable
       *       then you should also check for entities with entity state
       *       of Entity.STATUS_MODIFIED here as well.
       */
      if (product.getEntityState() == Entity.STATUS_NEW
          && product.getProdId().equals(productId)) {
        foundIt = true;
        break;
      }
    }
  }
  return foundIt;
}

現在、SRDemoアプリケーションではエンド・ユーザーは新製品を作成できませんが、今後、作成が可能になった場合に備え、検証を実装しておくことをお薦めします。executeQuery()に続くコードでは、候補となる製品IDが、キャッシュに存在する新しいProductエンティティのものであるかどうかをテストします。

検証ビュー・オブジェクトにはエンティティ・オブジェクトの慣用名がないため、その問合せはデータベース内の行を参照するのみです。そのため、データベースへの確認後にfoundIt変数がfalseになる場合、残りのコードが現在キャッシュ内にあるProductImplエンティティ行のイテレータを取得します。そして、行内をループし、新しいProductエンティティ行に候補となる製品IDが存在するかどうかを確認します。存在した場合、exists()メソッドはtrueを戻します。

9.6.3 指定された型のすべてのエンティティに関連する条件の検証

beforeCommit()メソッドは、保留中の変更リスト内の各エンティティ行の変更がデータベースに送信された後で、かつコミットされる前に実行されます。これは、指定した型のすべてのエンティティ行に対して規則を適用する必要のある、ビュー・オブジェクト・ベースの検証を実行するために最適なメソッドです。


注意:

beforeCommit()ロジックで例外ValidationExceptionがスローされた場合、構成中にjbo.txn.handleafterpostexcプロパティをtrueに設定する必要があります。これにより、フレームワークは現在のコミット・サイクルにおいて、データベースに送信済(かつコミット前)の他のエンティティ・オブジェクトのメモリー内の状態のロールバックを自動的に処理します。

9.7 アソシエーション・アクセッサを使用した、関連するエンティティ行へのアクセス方法

作成した検証規則や、導出された値のプログラムによるデフォルト設定には、関連するエンティティ行の確認が必要となる場合があります。これは、エンティティ・オブジェクトのカスタムJavaクラスのアソシエーション・アクセッサ・メソッドによって非常に簡単に処理できます。アクセッサ・メソッドをコールすると、アソシエーションのカーディナリティに応じて、すべての関連するエンティティ行、またはエンティティ行のRowSetに簡単にアクセスできます。

例9-10は、SRDemoアプリケーションのServiceHistoryエンティティ・オブジェクトで使用される、プログラムによるデフォルト設定のロジックを示しています。新規サービス履歴の行の行番号は、ServiceHistoryImpl型の親エンティティ行にアクセスし、そこで値を1つ増やす前にgetMaxHistoryLineNumber()というヘルパー・メソッドを実行して算出します。親エンティティ行がすでにキャッシュに存在する場合、アソシエーション・アクセッサはそこからキャッシュ内の行にアクセスします。キャッシュ内に存在しない場合は、主キーを使用してキャッシュにアクセスします。

例9-10 createメソッドにおける構成する側の親エンティティ行へのアクセス

// In ServiceHistoryImpl.java in SRDemo sample
protected void create(AttributeList nameValuePair) {
  super.create(nameValuePair);
  setSvhType(getDefaultNoteType());
  setCreatedBy(getCurrentUserId());
  setLineNo(new Number(getServiceRequest().getMaxHistoryLineNumber()+1));
}

例9-11は、ServiceRequestエンティティ・オブジェクトのカスタムJavaクラスにおける、getMaxHistoryLineNumber()のコードを示しています。アソシエーション・アクセッサの別の方法として、また、既存のサービス履歴行のLineNo属性の最大値を算出するため、子のServiceHistory行(ServiceHistoryImpl型)のRowSetを取得する方法も示しています。

例9-11 アソシエーション・アクセッサを使用した、値の算出における構成される側の子エンティティ行へのアクセス

// In ServiceRequestImpl.java in SRDemo Sample
public long getMaxHistoryLineNumber() {
  long max = 0;
  RowSet histories = (RowSet)getServiceHistories();
  if (histories != null) {
    while (histories.hasNext()) {
      long curLine = ((ServiceHistoryImpl)histories.next()).getLineNo()
                                                           .longValue();
      if (curLine > max) {
        max = curLine;
      }
    }
  }
  histories.closeRowSet();
  return max;
}

9.8 認証されたユーザーに関する情報の参照方法

jbo.security.enforce実行時の構成プロパティをMustまたはAuthのいずれかの値に設定している場合、oracle.jbo.server.SessionImplオブジェクトは、認証されたユーザーの名前と、そのユーザーの属するロールの情報を取得するメソッドを提供します。これは、クライアントがアクセス可能なoracle.jbo.Sessionインタフェースの実装クラスです。

9.8.1 認証されたユーザーのロールに関する情報の参照

oracle.jbo.Sessionインタフェースでは、次の2つのメソッドを提供します。

  • String[] getUserRoles()は、ユーザーが属するロール名の配列を戻します。

  • boolean isUserInRole(String roleName)は、ユーザーが指定されたロールに属する場合にtrueを戻します。

エンティティ・オブジェクト・コードは、次をコールしてSessionにアクセスできます。

Session session = getDBTransaction().getSession();

例9-12に、この方法を使用しているヘルパー・メソッドを示します。このメソッドは、isUserInRole()メソッドによって現在のユーザーがtechnicianロールに属しているかをテストして、ユーザーが技術者かどうかを判断します。

例9-12 認証されたユーザーが指定されたロールに属するかどうかをテストするヘルパー・メソッド

protected boolean currentUserIsTechnician() {
  return getDBTransaction().getSession().isUserInRole("technician");
}

定数を別々のSRConstantsクラスにリファクタすると、SRDemoアプリケーションでは、SREntityImplベース・クラスに次のようなヘルパー・メソッドが含まれます。サンプルのすべてのエンティティ・オブジェクトは、この一般的な機能を継承するように拡張されています。

protected boolean currentUserIsTechnician() {
  return getDBTransaction().getSession()
                           .isUserInRole(SRConstants.TECHNICIAN_ROLE);
}
protected boolean currentUserIsManager() {
  return getDBTransaction().getSession()
                           .isUserInRole(SRConstants.MANAGER_ROLE);
}
protected boolean currentUserIsCustomer() {
  return getDBTransaction().getSession()
                           .isUserInRole(SRConstants.USER_ROLE);
}
protected boolean currentUserIsStaffMember() {
  return currentUserIsManager() || currentUserIsTechnician();
}

これらのメソッドは、現在のユーザーのロールに基づく、サービス・リクエスト・タイプの条件付きのデフォルトの設定のため、create()メソッドで使用されます。getDefaultNoteType()ヘルパー・メソッドは次のようになります。

// In ServiceHistoryImpl.java in SRDemo sample
private String getDefaultNoteType() {
  return currentUserIsStaffMember() ? TECHNICIAN_TYPE : CUSTOMER_TYPE;
}

これはServiceHistoryエンティティ・オブジェクトのオーバーライドされたcreate()メソッドにより、現在のユーザーのロールに基づくサービス履歴タイプのデフォルトの設定のために使用されます。

// In ServiceHistoryImpl.java in SRDemo sample
protected void create(AttributeList nameValuePair) {
  super.create(nameValuePair);
  setSvhType(getDefaultNoteType());
  setCreatedBy(getCurrentUserId());
  setLineNo(new Number(getServiceRequest().getMaxHistoryLineNumber()+1));
}

9.8.2 認証されたユーザーの名前の参照

認証されたユーザーの名前にアクセスするには、SessionインタフェースをそのSessionImpl実装クラスにキャストする必要があります。その後、getUserPrincipalName()メソッドを使用できます。例9-13は、現在のユーザー名を取得するためにエンティティ・オブジェクトで使用可能なヘルパー・メソッドを示しています。

例9-13 認証された現在のユーザー名にアクセスするヘルパー・メソッド

protected String getCurrentUserName() {
  SessionImpl session = (SessionImpl)getDBTransaction().getSession();
  return session.getUserPrincipalName();
}

9.9 元の属性値へのアクセス方法

エンティティ属性値が現在のトランザクション内で変更されている場合、属性getterメソッドをその属性値に対してコールすると、この保留中の変更値が戻されます。getPostedAttribute()メソッドを使用すると、エンティティ・オブジェクトのビジネス・ロジックで、エンティティ行が変更される前にデータベースから読み取った、属性の元の値を確認できます。このメソッドは属性索引を引数としているため、JDeveloperが保持している、生成された属性の適切な索引定数を渡します。

9.10 現在のユーザー・セッションに関する情報の格納方法

現在のユーザー・セッションに関連した情報を、エンティティ・オブジェクトのビジネス・ロジックにより参照可能な方法で格納する必要がある場合、Sessionオブジェクトで提供されるユーザー・データのハッシュテーブルを使用できます。SRDemoアプリケーションによるハッシュテーブルの使用方法を検討してみます。新規ユーザーがアプリケーション・モジュールに初めてアクセスする場合、prepareSession()メソッドがコールされます。例9-14に示すように、SRServiceアプリケーション・モジュールはprepareSession()をオーバーライドし、LoggedInUserビュー・オブジェクト・インスタンスのretrieveUserInfoForAuthenticatedUser()メソッドをコールして、認証されたユーザーに関する情報を自動的に取得します。次に、ユーザーの数値IDをユーザー・データ・ハッシュテーブルに保存するため、setUserIdIntoUserDataHashtable()ヘルパー・メソッドをコールします。

例9-14 ユーザー情報の自動問合せのためのprepareSession()のオーバーライド

// In SRServiceImpl.java in SRDemo Sample
protected void prepareSession(Session session) {
  super.prepareSession(session);
  /*
   * Automatically query up the correct row in the LoggedInUser VO
   * based on the currently logged-in user, using a custom method
   * on the LoggedInUser view object component.
   */
  getLoggedInUser().retrieveUserInfoForAuthenticatedUser();
  setUserIdIntoUserDataHashtable();
}

例9-15に、LoggedInUserビュー・オブジェクトのretrieveUserInfoForAuthenticatedUser()メソッドのコードを示します。これは、それ自体のEmailAddressバインド変数を、セッションの認証されたユーザー名に設定し、executeQuery()をコールしてUSERS表から追加のユーザー情報を取得します。

例9-15 ユーザー詳細の追加取得のための、認証されたユーザー名へのアクセス

// In LoggedInUserImpl.java
public void retrieveUserInfoForAuthenticatedUser() {
  SessionImpl session = (SessionImpl)getDBTransaction().getSession();
  setEmailAddress(session.getUserPrincipalName());
  executeQuery();
  first();
}

LoggedInUserビュー・オブジェクトが取得する、認証されたユーザーの情報の1つには、メソッドの結果として戻されるユーザーの数値ID番号があります。たとえば、ユーザーskingには、数値のUserIdとして300が割り当てられています。

例9-16は、前述のprepareSession()コードで使用される、setUserIdIntoUserDataHashtable()ヘルパー・メソッドを示しています。このメソッドでは、文字列定数SRConstants.CURRENT_USER_IDで指定されるキーを使用して、数値ユーザーIDをユーザー・データ・ハッシュテーブルに格納します。

例9-16 エンティティ・オブジェクトからのアクセスのためのユーザー・データ・ハッシュテーブルへの情報の設定

// In SRServiceImpl.java
private void setUserIdIntoUserDataHashtable() {
  Integer userid = getUserIdForLoggedInUser();
  Hashtable userdata = getDBTransaction().getSession().getUserData();
  if (userdata == null) {
    userdata = new Hashtable();
  }
  userdata.put(SRConstants.CURRENT_USER_ID, userid);
}

ServiceRequestエンティティ・オブジェクトおよびServiceHistoryエンティティ・オブジェクトでは、どちらも次のようなヘルパー・メソッドを使用して数値ユーザーIDを参照するようcreate()メソッドがオーバーライドされています。オーバーライドされたメソッドでは、CreatedBy属性を現在認証されているユーザーの数値ユーザーIDにプログラム的に設定します。

protected Number getCurrentUserId() {
  Hashtable userdata = getDBTransaction().getSession().getUserData();
  Integer userId = (Integer)userdata.get(SRConstants.CURRENT_USER_ID);
  return userdata != null ? Utils.intToNumber(userId):null;
}

9.11 現在の日時へのアクセス方法

エンティティ・オブジェクトのビジネス・ロジックにおいては、現在の日時の参照が役立つ場合があります。例9-17では、時間情報を含めず、現在日付にのみヘルパー・メソッドでアクセスする方法について示しています。

例9-17 時間情報を含めず、現在日付にアクセスするヘルパー・メソッド

/*
 * Helper method to return current date without time
 *
 * Requires import: oracle.jbo.domain.Date
 */
protected Date getCurrentDate() {
  return new Date(Date.getCurrentDate());
}

一方、現在の日付に時間情報も含める必要がある場合、例9-18で示すヘルパー・メソッドを使用してください。

例9-18 時間情報を含む現在日付にアクセスするヘルパー・メソッド

/*
 * Helper method to return current date with time
 *
 * Requires imports: oracle.jbo.domain.Date
 *                   java.sql.Timestamp
 */
protected Date getCurrentDateWithTime() {
  return new Date(new Timestamp(System.currentTimeMillis()));
}

9.12 正常に完了したコミットに関する通知の送信方法

afterCommit()メソッドは、保留中の変更リスト内にあり、データベースに正常に保存された各エンティティ行に対して起動します。このメソッドを使用して、エンティティの状態の変化を電子メールで通知できます。

9.13 エンティティ行削除の条件付き禁止方法

remove()メソッドは、エンティティ行削除の前に、その行に対して起動します。このメソッドでは、条件付きでJboExceptionをスローし、該当する条件を満たさない場合は行の削除を回避するよう設定できます。


注意:

このエンティティ・オブジェクトでは、既存の構成される側の子行がある場合、マスター・エンティティ行の削除が宣言的に回避されます。この構成に対して、このオプションはアソシエーション・エディタ「アソシエーション・プロパティ」ページで設定できます。

9.14 属性に対する条件付きの更新可能性の実装方法

指定された属性を更新可能にするかどうかを、実行時に適切な条件に従ってプログラム的に判断するために、エンティティ・オブジェクト・クラス内のisAttributeUpdateable()メソッドをオーバーライドできます。例9-19は、現在の認証されたユーザーがスタッフ・メンバーである場合にのみSvhType属性を更新可能にするため、SRDemoアプリケーション内のServiceHistoryエンティティ・オブジェクトにより、このメソッドがどのようにオーバーライドされるかを示しています。エンティティ・オブジェクトでは、このメソッドを実行する際、更新可能性の確認対象となる整数属性索引を渡します。特定の属性に対する条件付きの更新可能性ロジックは、属性索引に基づいて、if文またはswitch文内に実装します。ここでは、SVHTYPEがエンティティ・オブジェクトのカスタムJavaクラスに自動的に保持されている整数属性の索引定数を参照します。

例9-19 属性の更新可能性の実行時の条件付きの判断

// In ServiceHistoryImpl.java
public boolean isAttributeUpdateable(int index) {
  if (index == SVHTYPE) {
    if (!currentUserIsStaffMember()) {
      return super.isAttributeUpdateable(index);
    }
    return CUSTOMER_TYPE.equals(getSvhType()) ? false : true;
  }
  return super.isAttributeUpdateable(index);
}

注意:

エンティティ・ベースのビュー・オブジェクトは、エンティティ・オブジェクト内でカプセル化された他の要素と同様に、この条件付きの更新可能性も継承します。この種の条件付き更新可能性ロジックを、一時ビュー・オブジェクト属性に固有の方法で実装する必要がある場合、または、ビュー・オブジェクト内の複数のエンティティ・オブジェクトのデータを含む一部の条件を適用する場合、ビュー・オブジェクトのビュー行クラス内の同じメソッドをオーバーライドすると、目的の結果が得られます。

9.15 その他のリソース

Oracle Consultingによる『Business Rules in ADF Business Components』ホワイトペーパーでは、Oracle ADFを使用してプロジェクトを実装した際に直面したほぼすべての種類の実際のビジネス・ルールを、ADF Business Componentsを使用して分類および実装するための正式なアプローチについて説明しています。このホワイトペーパーには、OTNのOracle JHeadstartプロダクト・センターからアクセスできます。