読者です 読者をやめる 読者になる 読者になる

kagamihogeの日記

kagamihogeの日記です。

The Java EE 7 TutorialのjBatchの章をテキトーに訳した

jBatchのお勉強のついでにThe Java EE 7 TutorialThe Java EE 7 Tutorial:Batch Processing | Java EE Documentationの章をテキトーに訳した。

なお、ヒジョーにビミョーで機械翻訳よりはごましお程度にマシな精度な品質なので、日本語が歪んでいたり表記がブレいていたりしてもそこのところは容赦願いたい。

55 バッチ処理(Batch Processing)

この章は、バッチジョブの実行・実装・定義のサポートを提供する、Javaプラットフォームでのバッチアプリケーション(JSR-352)について解説します。バッチジョブとは、ユーザーとの対話操作無しに実行が可能なタスクのことです。このバッチフレームワークの構成要素は、XMLベースのジョブ定義言語、Java API、バッチランタイム、によって成り立っています。

大抵のエンタープライズアプリケーションは、ユーザーとの対話操作無しに実行が可能なタスクを含んでいます。それらのタスクは、定期的かリソース消費が少ない時間帯に実行されたり、ログファイル・データベース・画像のような大量データ処理を時には実行します。例としては、請求・レポート生成・データフォーマット変換・画像処理、など。これらのタスクはバッチジョブ(batch jobs)と呼ばれます。

バッチ処理とは、コンピューター上でバッチジョブを実行すること、を指します。Java EEは、すべてのバッチアプリケーションに共通なバッチ実行のための基盤を提供するバッチ処理フレームワークを含んでおり、それらは開発者がバッチアプリケーション上でのビジネスロジックに集中できるようにします。バッチフレームワークは、XMLベースのジョブ定義言語、バッチジョブの実行を管理するコンテナ、バッチコンテナを操作するためのインターフェースとクラス、から構成されています。

以降の章へのリンクは下記の通りです。*1

  • バッチ処理入門 (Introduction to Batch Processing)
  • Java EEでのバッチ処理 (Batch Processing in Java EE)
  • 簡単な使用例(Simple Use Case)
  • ジョブ定義言語の使用 (Using the Job Specification Language)
  • バッチアーティファクトの作成 (Creating Batch Artifacts)
  • バッチランタイムへのサブミット (Submitting Jobs to the Batch Runtime)
  • バッチアプリケーションのパッケージ作成 (Packaging Batch Applications)
  • The webserverlog Example Application
  • The phonebilling Example Application
  • Further Information about Batch Processing

55.1 バッチ処理入門 (Introduction to Batch Processing)

バッチジョブ(job)は、ユーザーが操作することなく完了できます。たとえば、通話料金請求アプリケーションはエンタープライズ情報システムから電話番号レコードを読み出し、アカウントごとに月次請求を生成します。このアプリケーションはユーザ操作を要求せず、バッチジョブとして動作します。

通話料金請求アプリケーションは2フェーズで構成されます。最初のフェーズでは、個々の発信情報と月次請求とを結び付け、次のフェースでは、各請求ごとに税金と合計金額を計算します。それぞれのフェーズがバッチジョブのステップ(step)です。

バッチアプリケーションはステップの集まりとそれらの実行順序によって定義します。他のバッチフレームワークによっては条件分岐のような追加要素があるかもしれないし、ステップのグループを並列実行させられるかもしれません。以降の章はステップの詳細について説明し、バッチフレームワークのその他の共通な特徴についての情報を提供します。

55.1.1 バッチジョブにおけるステップ(Steps in Batch Jobs)

ステップとは、バッチジョブの独立しているシーケンシャルなフェーズのことです。バッチジョブには、チャンク指向(chunk-oriented)とタスク指向(task-oriented)のステップが含まれます。

  • チャンク指向のステップ(Chunk-oriented steps)とは、データソースから複数件読み込みによるデータ処理をし、それらのデータにビジネスロジックを適用し、それから結果を保存することです。チャンクステップは、一度に一件読み込みんで処理をし、その結果をグループ化してチャンクに送ります。結果はチャンクが設定サイズに達したときに保存されます。チャンク指向の処理は結果の保存を効率的にし、トランザクション境界の管理を容易にします。チャンクステップは三つのパートに分かれます。
    • 入力・検索パートは、データソースから一度に一件の読み込みをします。データソースは例えば、データベース・エントリやディレクトリのファイルやログファイルのエントリが挙げられます。
    • ビジネスプロセスパートは、アプリケーションごとに定義されたビジネスロジックを使用して一度に一件の処理をします。例えば、フィルタリングやフォーマットや計算するためのデータアクセス、などです。
    • 出力・書き込みパートは、一度に一つのチャンクを保存します。チャンクには、処理された複数件が入れられています。
  • チャンクステップは、大量データ処理のためにしばしば長時間実行となります。バッチフレームワークは、チャンクステップがチェックポイント(checkpoints)を使用して処理の進行状況をブックマークできるようにします。中断されたチャンクステップは最後のチェックポイントから再開できます。チャンクステップの入力・検索と出力・書き込みパートは各チャンクの処理後に現在位置をセーブしておき、ステップの再開時に復元します。

Figure 55-1 バッチジョブにおけるチャンクステップ
http://docs.oracle.com/javaee/7/tutorial/doc/img/jeett_dt_058.png
Description of "Figure 55-1 Chunk Steps in a Batch Job"

  • タスク指向のステップ(Task-oriented steps)とは、データソースのデータを使用する処理以外のタスク実行のことです。たとえば、ディレクトリの作成や削除、ファイルの移動、データベーステーブルの作成や削除、リソースの設定、などです。タスクステップは、チャンクステップに比べるとたいていは短時間です。

例えば、通話料金請求アプリケーションは二つのチャンクステップから構成されます。

  • 最初のステップでは、入力・検索パートはレジストリから通話記録を読み出し、ビジネスプロセスパートは通話と請求とを結びつけてもしアカウントが存在しない場合には請求を作成*2し、そして出力・書き込みパートはデータベースに請求を保存します。
  • 次のステップでは、入力・検索パートはデータベースから請求を読み出し、ビジネスプロセスパートでは税金と請求ごとの合計金額を計算し、最後に出力・書き込みパートはデータベースのレコードを更新して請求書を印刷可能にします。 このアプリケーションはまた、前月に生成された請求からファイルをクリーンナップするタスクステップも含みます

55.1.2 ステータスと条件分岐(Status and Decision Elements)*3

バッチフレームワークはジョブでのステップごとにステータス(status)を記録し続けます。ステータスはステップが実行中か完了したかを示します。もしステップが完了した場合、ステータスは次のうちいずれか一つを意味します。ステップの実行が正常終了した・ステップが中断された・エラーが発生した。

ステップに加えて、バッチジョブには条件分岐(decision elements)を作れます。条件分岐は、直前ステップの完了ステータスを使用して、次のステップを進めるかバッチジョブを終了させるか、といったことが出来ます。条件分岐はバッチジョブが終了したときのステータスを設定します。ステップと同様に、バッチジョブは正常終了することもあれば、中断インタラプトされたり失敗することもあります。

Figure 55-2は、チャンクステップ、タスクステップと条件武器を含むジョブの例を示しています。

Figure 55-2 ステップと条件分岐で構成されるジョブ

Description of "Figure 55-2 Steps and Decision Elements in a Job"

55.1.3 パラレル処理(Parallel Processing)

時にバッチジョブは大量データを処理したり、高負荷な処理を実行します。バッチアプリケーションは二つのシナリオにおいて並行処理から恩恵を受けられます。

  • 互いに依存しないステップは異なるスレッドで実行可能です。
  • 処理済アイテムの結果に依存せずに個々のアイテムに対して処理を行うチャンク指向ステップは複数のスレッドで実行可能です。 バッチフレームワークは、独立したステップをグループ化し、チャンク指向ステップを並行処理できるように分割するための仕組みを開発者に提供します。

55.1.4 バッチフレームワークの機能(Batch Framework Functionality)

バッチアプリケーションは以下のように共通の要求を持っています。

  • ジョブ・ステップ・条件分岐およびそれらの関係を定義すること。
  • ステップのグループ実行やステップのパートをパラレル処理すること。
  • ジョブとステップのステータス情報と管理すること。
  • ジョブの起動と中断されたジョブを再開すること。
  • エラーハンドリング。

バッチフレームワークはすべてのバッチアプリケーションに共通の要求に応えるためのバッチ実行インフラを提供し、開発者をアプリケーションのビジネスロジックに集中出来るようにします。バッチフレームワークは、ジョブとステップを記述するためのフォーマット、API、バッチジョブの実行を管理するサービス、から構成されます。

55.2 Java EEでのバッチ処理 (Batch Processing in Java EE)

この章ではJava EEバッチ処理フレームワークコンポーネント一覧とバッチアプリケーション開発に必要なステップの概要について解説します。

55.2.1 バッチ処理フレームワーク(The Batch Processing Framework)

Java EEバッチ処理フレームワークは以下の要素から構成されます。

  • ジョブの実行を管理するバッチランタイム。
  • XMLベースのジョブ定義言語。
  • バッチランタイム操作用のJava API
  • ステップ、条件分岐、その他バッチアーティファクトを実装するためのJava API

Java EEのバッチアプリケーションはXMLファイルとJavaクラスから構成されます。XMLファイルはバッチアーティファクトを一単位とするジョブの構造とそれらの関係を定義します。(バッチアーティファクトとは、チャンク指向ステップ・タスク指向ステップ・条件分岐およびその他のバッチアプリケーションのコンポーネントの一部分のことです。)JavaクラスはXMLファイルで定義されたバッチアーティファクトのアプリケーションロジックを実装します。バッチランタイムはXMLファイルをパースし、バッチアプリケーションでジョブを実行するためにJavaクラスとしてバッチアーティファクトをロードします。

55.2.2 バッチアプリケーションの作成(Creating Batch Applications)

Java EEでバッチアプリケーションを作成するための手順は以下の通りです。

  1. バッチアプリケーションの設計
    1. 入力データの入手元とフォーマット、要求される処理結果、処理内容の設計。
    2. アプリケーションのジョブを、チャンク指向ステップ・タスク指向ステップ・条件分岐によって構成し、それらの間の依存関係を決定する。
    3. ステップ遷移を一単位とする実行順序を決定する。
    4. パラレル実行可能なステップと一つ以上のスレッドで実行可能なステップを特定する。
  2. ステップや条件分岐などをフレームワークが指定するインターフェースを実装したJavaクラスとしてバッチアーティファクトを作成する。それらのJavaクラスに含まれるコードは、入力ソースからデータを読み込み、何らかの処理を行って結果を保存します。バッチアーティファクトはDIを使用してバッチランタイムからコンテキストオブジェクトを取得可能です。
  3. ジョブ定義言語を使用してジョブとステップの実行フローをXMLファイルで定義します。XMLファイルの要素はJavaクラスとして実装されたバッチアーティファクトを参照します。バッチアーティファクトは、XMLファイルで定義されたファイル名やデータベースなどのプロパティにアクセス可能です。
  4. バッチランタイムが提供するJava APIを使用してバッチアプリケーションを実行します。

以降の節では、バッチアプリケーションを作成するためのJava EEバッチ処理フレームワークコンポーネントの使用方法の詳細について解説します。

55.2.3 バッチジョブの要素(Elements of a Batch Job)

バッチジョブは以下の要素のうち一つ以上から構成されます。

  • ステップ(Steps)
  • フロー(Flows)
  • スプリット(Splits)
  • 条件分岐(Decision elements)

バッチ処理入門 (Introduction to Batch Processing)で解説したステップは、チャンク指向にもタスク指向にも出来ます。チャンク指向ステップはパーティーションステップ(partitioned steps)として定義できます。パーティーションチャンクステップでは、あるアイテムの処理をするとき他のアイテムに依存しないため、ステップは一つ以上のスレッドで実行可能です。

フロー(flow)とは、一単位として実行されるステップのシーケンスです。関連するステップのシーケンスはグループ化されて一つのフローになります。フローのステップは、そのフロー外のステップへは遷移出来ません。フローの最終ステップが完了したとき、フローは次の要素へと遷移します。

スプリット(split)とは、パラレルに実行するフローの組み合わせです。フローはそれぞれ別のスレッドで実行されます。すべてのフローが完了したとき、スプリットは次に要素へと遷移します。

条件分岐は、前のステップの完了ステータスを使用して、次のステップに進むかバッチジョブを終了するかを決定します。

55.2.4 プロパティとパラメーター(Properties and Parameters)

ジョブとステップはプロパティを持てます。ジョブ定義ファイルにプロパティを定義し、バッチアーティファクトはバッチランタイムからコンテキストオブジェクトを使用してプロパティを取得できます。プロパティを使用することで、ビジネスロジックからジョブの静的なパラメーターを分離し、異なるジョブ定義ファイルでバッチアーティファクトが再利用可能になります。

「55.4 ジョブ定義言語の使用」で、プロパティの詳細について、「55.5 バッチアーティファクトの作成」でプロパティの取得方法について、解説します。

Java EEアプリケーションはまた、バッチランタイムへジョブがサブミットされたとき、ジョブへパラメーター(parameters)を渡せます。これにより、実行時にのみ有効な動的パラメーターを定義可能です。パラメータはまた、パーティーションステップに必要不可欠で、たとえば各パーティーションが要求する処理範囲のために使用されます。

「55.6 バッチランタイムへのサブミット」で、ジョブがサブミットされるときのパラメータについて解説します。「55.9 The phonebilling Example Application」で、パーティーションステップ用のパラメータ定義とバッチアーティファクトでのパラメータ取得方法について解説します。

55.2.5 ジョブインスタンスと実行(Job Instances and Job Executions)

ジョブ定義は複数インスタンス(instances)と各インスタンスごとに異なるパラメータを持てます。ジョブの実行(execution)とは、ジョブインスタンスを実行することを指します。バッチランタイムは、「55.6.2 Checking the Status of a Job」で解説されるように、ジョブ実行とジョブインスタンスについての情報をメンテナンスしています。

55.2.6 バッチと完了ステータス(Batch and Exit Status)

ジョブのステータス、ステップ、スプリット、フローはバッチランタイム上ではバッチステータス(batch status)として表現されます。バッチステータスの値はTable 55-1に示すとおりで、これらは文字列です。

Table 55-1 バッチステータスの値

説明
STARTING ジョブがバッチランタイムへサブミット済
STARTED ジョブが実行中
STOPPING ジョブが停止命令を受け取り済
STOPPED ジョブが停止済
FAILED ジョブがエラー終了
COMPLETED ジョブが正常終了
ABANDONED 破棄されたジョブ

「55.6 バッチランタイムへのサブミット」で解説されるように、Java EEアプリケーションはJobOperatorインターフェースを使用してジョブのバッチステータスを取得できます。「55.4 ジョブ定義言語の使用」で解説されるように、ジョブ定義ファイルはジョブ定義言語(JSL:Job Specification Language)を使用してバッチステータスを参照できます。「55.5.3 Using the Context Objects from the Batch Runtime」で解説されるように、バッチアーティファクトはコンテキストオブジェクトを使用してバッチステータスを取得できます。

フローでは、バッチステータスはフローでの最終ステップのものを指します。スプリットでは、バッチステータスは以下の通りです。

  • COMPLETED - 全フローがCOMPLETEDである。
  • FAILED - フローのいずれかがFAILEDである。
  • STOPPED - フローのいずれかがSTOPPEDであり、FAILEDが一つも無い。

ジョブ・ステップ・スプリット・フローのバッチステータスはバッチランタイムによって設定されます。ジョブ・ステップ・スプリット・フローはまた、バッチステータスベースのユーザ定義値である完了ステータス(exit status)持てます。完了ステータスはジョブ定義ファイルもしくはバッチアーティファクトで設定出来ます。それらの完了ステータスは、先に述べたバッチステータスと同様の方法で取得できます。完了ステータスのデフォルト値はバッチステータスと同じです。

55.3 簡単な使用例(Simple Use Case)

この章では、ジョブ定義言語(JSL)を使用したジョブとバッチアーティファクト定義の簡単な使用例について解説します。バッチフレームワークの要素の詳細についてはこの章の他の節を参照してください。

以下は、チャンクステップとタスクステップのジョブ定義です。

<?xml version="1.0" encoding="UTF-8"?>
<job id="simplejob" xmlns="http://xmlns.jcp.org/xml/ns/javaee" 
                    version="1.0">
  <properties>
    <property name="input_file" value="input.txt"/>
    <property name="output_file" value="output.txt"/>
  </properties>
  <step id="mychunk" next="mytask">
    <chunk>
      <reader ref="MyReader"></reader>
      <processor ref="MyProcessor"></processor>
      <writer ref="MyWriter"></writer>
    </chunk>
  </step>
  <step id="mytask">
    <batchlet ref="MyBatchlet"></batchlet>
    <end on="COMPLETED"/>
  </step>
</job>

55.3.1 チャンクステップ(Chunk Step)

多くの場合、チャンク指向ステップのためにチェックポイントクラスを実装すべきです。以下のクラスは、テキストファイルの列番号を保持します。

public class MyCheckpoint implements Serializable {
    private long lineNum = 0;
    public void increase() { lineNum++; }
    public long getLineNum() { return lineNum; }
}

以下のItemReader実装クラスは、ジョブが再開された場合には与えられたチェックポイントから入力ファイルの読み込みを続行します。アイテム*4はテキストファイルの各行に対応します。より複雑なシナリオでは、アイテムは独自のJavaクラスで入力元はデータベース等になります。

@Dependent
@Named("MyReader")
public class MyReader implements javax.batch.api.chunk.ItemReader {
    private MyCheckpoint checkpoint;
    private BufferedReader breader;
    @Inject
    JobContext jobCtx;

    public MyReader() {}

    @Override
    public void open(Serializable ckpt) throws Exception {
        if (ckpt == null)
            checkpoint = new MyCheckpoint();
        else
            checkpoint = (MyCheckpoint) ckpt;
        String fileName = jobCtx.getProperties()
                                .getProperty("input_file");
        breader = new BufferedReader(new FileReader(fileName));
        for (long i=0; i<checkpoint.getLineNum(); i++)
            breader.readLine();
    }

    @Override
    public void close() throws Exception {
        breader.close();
    }

    @Override
    public Object readItem() throws Exception {
        String line = breader.readLine();
        return line;
    }
}

このケースでは、ItemProcessorの実装クラスは読み込んだ行を大文字に変換するだけです。より複雑な例では、様々なやり方でアイテムを処理したり、独自のJavaクラスへ変換したりします。

@Dependent
@Named("MyProcessor")
public class MyProcessor implements javax.batch.api.chunk.ItemProcessor {
    public MyProcessor() {}

    @Override
    public Object processItem(Object obj) throws Exception {
        String line = (String) obj;
        return line.toUpperCase();
    }
}

Note:バッチ処理APIジェネリクスをサポートしません。多くの場合、processItemの引数を適切な型へとキャストしなければなりません。

ItemWriterの実装クラスはItemProcessorを通過したデータをファイルへ出力します。もしチェックポイントが無ければ出力ファイルを上書きし、そうでなければ、ファイルのEOFへの書き込みを再開します。アイテムはチャンクごとに書き込まれます。

@Dependent
@Named("MyWriter")
public class MyWriter implements javax.batch.api.chunk.ItemWriter {
    private BufferedWriter bwriter;
    @Inject
    private JobContext jobCtx;

    @Override
    public void open(Serializable ckpt) throws Exception {
        String fileName = jobCtx.getProperties()
                                .getProperty("output_file");
        bwriter = new BufferedWriter(new FileWriter(fileName, 
                                                    (ckpt != null)));
    }

    @Override
    public void writeItems(List<Object> items) throws Exception {
        for (int i = 0; i < items.size(); i++) {
            String line = (String) items.get(i);
            bwriter.write(line);
            bwriter.newLine();
        }
    }

    @Override
    public Serializable checkpointInfo() throws Exception {
        return new MyCheckpoint();
}

55.3.2 タスクステップ(Task Step)

タスクステップは出力ファイルのサイズを表示します。より複雑なシナリオでは、タスクステップはチャンク指向ではうまく扱えないタスクを実行します。

@Dependent
@Named("MyBatchlet")
public class MyBatchlet implements javax.batch.api.chunk.Batchlet {
    @Inject
    private JobContext jobCtx;
    
    @Override
    public String process() throws Exception {
        String fileName = jobCtx.getProperties()
                                .getProperty("output_file");
        System.out.println(""+(new File(fileName)).length());
        return "COMPLETED";
    }
}

55.4 ジョブ定義言語の使用 (Using the Job Specification Language)

ジョブ定義言語(JSL)は、XMLファイルでジョブのステップと実行順序を定義します。以下の例は、一つのチャンクステップと一つのタスクステップから構成されるジョブの定義方法を示しています。

<job id="loganalysis" xmlns="http://xmlns.jcp.org/xml/ns/javaee" 
                      version="1.0">
  <properties>
    <property name="input_file" value="input1.txt"/>
    <property name="output_file" value="output2.txt"/>
  </properties>

  <step id="logprocessor" next="cleanup">
    <chunk checkpoint-policy="item" item-count="10">
      <reader ref="com.xyz.pkg.LogItemReader"></reader>
      <processor ref="com.xyz.pkg.LogItemProcessor"></processor>
      <writer ref="com.xyz.pkg.LogItemWriter"></writer>
    </chunk>
  </step>

  <step id="cleanup">
    <batchlet ref="com.xyz.pkg.CleanUp"></batchlet>
    <end on="COMPLETED"/>
  </step>
</job>

この例は、loganalysisバッチジョブを定義しています。このジョブは、logprocessorチャンクステップとcleanupタスクステップから構成されます。logprocessorステップはcleanupステップに遷移し、処理が完了したらジョブを終了します。

job要素は二つのプロパティinput_fileoutput_fileを定義しています。プロパティ定義を使用することで、Javaのバッチアーティファクトを再コンパイルすることなく異なる設定パラメータでバッチジョブを実行できます。バッチアーティファクトはバッチランタイムのコンテキストオブジェクトを使用してそれらのプロパティを取得できます。

logprocessorステップは、読み込みのためのLogItemReaderビジネスロジックのためのLogItemProcessor、書き込みのためのLogItemWriter、のバッチアーティファクトから構成されるチャンクステップです。このステップは10アイテムを処理するごとにチェックポイントを作成します。

cleanupステップは、CleanUpクラスとして定義されるバッチアーティファクトから成るタスクステップです。ジョブはこのステップが完了したときに終了します。 次の節ではジョブ定義言語(JSL)の詳細について解説し、よく使う属性と子要素について示します。

55.4.1 ジョブ要素(The job Element)

job要素は、常にジョブ定義ファイルのトップレベル要素です。主要な属性はidrestartableです。job要素は、一つのproperties要素と、ゼロ個以上のlistenerstepflowsplit要素を持つことが出来ます。例として、

<job id="jobname" restartable="true">
  <listeners>
    <listener ref="com.xyz.pkg.ListenerBatchArtifact"/>
  </listeners>
  <properties>
    <property name="propertyName1" value="propertyValue1"/>
    <property name="propertyName2" value="propertyValue2"/>
  </properties>
  <step ...> ... </step>
  <step ...> ... </step>
  <decision ...> ... </decision>
  <flow ...> ... </flow>
  <split ...> ... </split>
</job>

listener要素は、ジョブの実行前後に呼び出されるメソッドを持つバッチアーティファクトを定義します。バッチアーティファクトjavax.batch.api.listener.JobListenerインタフェースの実装です。ジョブリスナー実装の例は The webserverlog Example ApplicationThe webserverlog Example Application を参照してください。

job要素の内側の最初のstepflowsplit要素が最初に実行されます。

55.4.2 ステップ要素(The step Element)

step要素は、jobflowの子要素になれます。主要な属性はidnextです。step要素は以下の要素を持つことが出来ます。

  • チャンク指向ステップ用のchunk要素一つか、タスク指向ステップ用ののbatchlet要素一つ。
  • 一つのproperties要素(optional)。この要素は、バッチアーティファクトがバッチコンテキストオブジェクトを使用して取得できる、プロパティの組み合わせを定義します。
  • 一つのlistener要素(optional)。複数のリスナーを定義する場合はlisteners要素。この要素は、ステップ実行の様々なフェーズをインターセプトするリスナーアーティファクトを定義します。チャンクステップでは、リスナーのバッチアーティファクトは以下のインターフェースを実装します。
    StepListener, ItemReadListener, ItemProcessListener, ItemWriteListener, ChunkListener, RetryReadListener, RetryProcessListener, RetryWriteListener, SkipReadListener, SkipProcessListener, SkipWriteListener.
    タスクステップでは、リスナーのバッチアーティファクトStepListenerインタフェースを実装します。
    リスナー実装の例はThe webserverlog Example ApplicationThe Listener Batch Artifactsを参照してください。
  • 一つのpartition要素(optional)。この要素は、一つ以上のスレッドで実行されるpartitionedステップのために使用します。
  • 一つのend要素。そのステップがジョブの最後の要素の場合に使用します。この要素はバッチステータスをCOMPLETEDに設定します。
  • 一つのstop要素(optional)。そのステップでジョブをstopする場合に使用します。この要素はバッチステータスをSTOPPEDに設定します。
  • 一つのfail要素(optional)。そのステップでジョブをterminateする場合に使用します。この要素はバッチステータスをFAILEDに設定します。
  • 一つ以上のnext要素。next属性が指定されていない場合に使用されます。この要素は、完了ステータスと関連付け、別のステップ、フロー、スプリットあるいは条件分岐を参照します。 以下はチャンクステップの例です。
<step id="stepA" next="stepB">
  <properties> ... </properties>
  <listeners>
    <listener ref="MyItemReadListenerImpl"/>
    ...
  </listeners>
  <chunk ...> ... </chunk>
  <partition> ... </partition>
  <end on="COMPLETED" exit-status="MY_COMPLETED_EXIT_STATUS"/>
  <stop on="MY_TEMP_ISSUE_EXIST_STATUS" restart="step0"/>
  <fail on="MY_ERROR_EXIT_STATUS" exit-status="MY_ERROR_EXIT_STATUS"/>
</step>

以下はタスクステップの例です。

<step id="stepB" next="stepC">
  <batchlet ...> ... </batchlet>
  <properties> ... </properties>
  <listener ref="MyStepListenerImpl"/>
</step>

55.4.2.1 チャンク要素(The chunk Element)

チャンク指向ステップ用に、step要素の子要素にchunkを作れます。この要素の属性一覧をTable 55-2に示します。

Table 55-2 チャンク要素の属性(Attributes of the chunk Element)

属性名 説明 デフォルト値
checkpoint-policy チャンクごとの処理結果のコミット方法を指定します。
"item" - チャンクはitem-count数処理後にコミットされます。
"custom" - チャンクはcheckpoint-algorithm要素で指定されるチェックポイントアルゴリズムに従ってコミットされます。
チェックポイントは、チャンクの結果がコミットされた時に更新されます。すべてのチャンクはJava EEのグローバルトランザクションで処理されます。もし、チャンク中の一アイテムが処理に失敗した場合、トランザクションロールバックし、このチャンクで処理されたアイテムは保存されません。
"item"
item-count チャンクがコミットされてチェックポイントが更新される処理数を指定します。 10
time-limit checkpoint-policy="item"のとき、チャンクがコミットされてチェックポイントが更新される秒数を指定します。
もし、time-limit秒で処理されていないitem-countアイテムがある場合、チャンクはコミットされてチェックポイントが更新されます。
0 (無制限)
buffer-items チェックポイントを更新するまで処理されたアイテムをバッファするかどうかを指定します。もしtrueの場合、チャンクがコミットされてチェックポイントが更新されるまえに、バッファされたアイテムのリストで作られたアイテムライターが一度だけ呼び出されます*5 true
skip-limit チャンク処理中にそのステップでスキップするスキップ可能例外(skippable exceptions)の数を指定します。スキップ可能例外クラスはskippable-exception-classes要素で指定します。 無制限
retry-limit リトライ可能例外(retryable exceptions)が発生したときの、ステップの試行回数を指定します。リトライ可能例外クラスはretryable-exception-classes要素で指定します。 無制限

chunk要素は以下の要素から構成されます。

  • 一つのreader要素。
    ItemReaderインターフェースを実装したバッチアーティファクトを指定します。
  • 一つのprocessor要素。
    ItemProcessorインタフェースを実装したバッチアーティファクトを指定します。
  • 一つのwriter要素。
    ItemWriterインタフェースを実装したバッチアーティファクトを指定します。
  • 一つのcheckpoint-algorithm要素(optional)。
    CheckpointAlgorithmインタフェースを実装したバッチアーティファクトを指定し、checkpoint-policyをcustomにする時に使用します。
  • 一つskippable-exception-classes要素(optional)。
    この要素は、チャンク処理をスキップすべき、reader・writer・processorバッチアーティファクトからスローされる例外の組み合わせを指定します。chunk要素のskip-limit属性にはスキップする例外の最大数を指定します。
  • 一つのretryable-exception-classes要素(optional)。
    この要素は、チャンク処理をリトライするために、reader・writer・processorバッチアーティファクトからスローされる例外の組み合わせを指定します。chunk要素のretry-limit属性には最大試行数を指定します。
  • 一つのno-rollback-exception-classes要素(optional)。この要素は、バッチランタイムが現在のチャンクをロールバックし、ロールバック無しで現在のオペレーションをリトライしないように、reader・writer・processorバッチアーティファクトからスローされる例外の組み合わせを指定します。
    この要素で指定されない例外では、例外発生時には現在のチャンクはデフォルトでロールバックされます。

以下にチャンク指向ステップの例を示します。

<step id="stepC" next="stepD">
  <chunk checkpoint-policy="item" item-count="5" time-limit="180"
         buffer-items="true" skip-limit="10" retry-limit="3">
    <reader ref="pkg.MyItemReaderImpl"></reader>
    <processor ref="pkg.MyItemProcessorImpl"></processor>
    <writer ref="pkg.MyItemWriterImpl"></writer>
    <skippable-exception-classes>
      <include class="pkg.MyItemException"/>
      <exclude class="pkg.MyItemSeriousSubException"/>
    </skippable-exception-classes>
    <retryable-exception-classes>
      <include class="pkg.MyResourceTempUnavailable"/>
    </retryable-exception-classes>
  </chunk>
</step>

この例はreader・processor・writerアーティファクトを持つチャンクステップを定義しています。ステップはチェックポイントを更新し、5アイテムを処理するごとにチャンクをコミットします。MyItemSeriousSubExceptionを除いて、MyItemExceptionとすべてのサブタイプの例外を最大10回までスキップします。MyResourceTempUnavailable例外が発生したとき、最大3回までステップはチャンクをリトライします。

55.4.2.2 batchlet要素(The batchlet Element)

タスク指向ステップ用に、step要素の子要素にbatchletを作れます。この要素の属性はrefのみで、Batchletインターフェースを実装したバッチアーティファクトを指定します。batchlet*6要素はproperties要素を持てます。

以下はタスク指向ステップの例です。

<step id="stepD" next="stepE">
  <batchlet ref="pkg.MyBatchletImpl">
    <properties>
      <property name="pname" value="pvalue"/>
    </properties>
  </batchlet>
</step>

この例はバッチアーティファクトを指定したバッチステップを定義しています。

55.4.2.3 パーティーション要素(The partition Element)

partition要素はstepの子要素です。この要素はステップがパーティーションであることを示しています。多くのパーティーション・ステップは、処理済みアイテムの結果に依存しないアイテムを処理するチャンクステップです。ステップのパーティーション数を指定し、処理対象アイテムに固有の情報をパーティーションごとに与えます。たとえば、

  • アイテムの範囲。たとえば、パーティーション1は、1から500を処理し、パーテーション2は、501から1000を処理する。
  • 入力ソース。たとえば、パーティーション1はinput1.txtのアイテムを処理し、パーティーション2はinput2.txtのアイテムを処理する。

パーティーション数、アイテム数、パーティーション・ステップ用の入力ソースが開発やデプロイ時に分かっている場合、ジョブ定義ファイルにパーティーション固有の情報をプロパティとして設定し、ステップのバッチアーティファクトでプロパティを取得します。ランタイムは、ステップのバッチアーティファクト(reader, processor, writer)をパーティーションとしてインスタンスを多数生成し、アーティファクトインスタンスはパーティーションに固有のプロパティを受け取ります。

多くの場合、パーティーション数、アイテム数、パーティーション・ステップ用の入力ソースは実行時にのみ決定されます。ジョブ定義ファイルでパーティーション固有のプロパティを静的に指定する代わりに、実行時に任意のデーターソースにアクセスするバッチアーティファクトを作成し、必要とされるパーティーション数とパーティーションごとに処理すべきアイテムの範囲を決められます。このバッチアーティファクトPartitionMapperインターフェースを実装します。バッチランタイムはこのアーティファクトを実行し、パーティーション用のステップのバッチアーティファクト(reader, writer, processor)をインスタンス化する情報を使用して、パーティーション固有のデータを引数としてバッチアーティファクトに渡します。

この節の残りはpartition要素の詳細について説明し、ジョブ定義ファイルの例を二つ示します。一つはパーティションごとにアイテムの範囲をパーティーション用プロパティで指定し、もう一つはパーティーション固有情報を指定するためにcode>PartitionMapper```の実装を使用します。

パーティーションチャンクステップの複雑な例はThe phonebilling Example ApplicationThe Phone Billing Chunk Stepを参照してください。

partition要素は以下の要素を持つことができます。

  • 一つのplan要素、ただしmapper要素が無い場合。
    この要素は、パーティーションごとのプロパティ・パーティーション数・スレッド数を、ジョブ定義ファイルで指定します。plan要素は設定値が開発かデプロイ時に分かっている場合に役立ちます。
  • 一つのmapper要素、ただしplan要素が無い場合。
    この要素は、パーティーションごとのプロパティ・パーティーション数・スレッド数を与えるバッチアーティファクトを指定します。バッチアーティファクトPartitionMapperインタフェースの実装です。パーティーションごとに要求される情報が実行時にのみ判明するときにこのオプションを使用します。
  • 一つのreducer要素(optional)。
    この要素はパーティーションステップの開始・終了・ロールバック時のコントロールを実行するバッチアーティファクトを指定します。異なるパーティーションからの結果をマージしたり他の関連オペレーションを実行したりすることができます。このバッチアーティファクトPartitionReducerインターフェースを実装します。
  • 一つのcollector要素(optional)。
    この要素は各パーティーションからanalyzerパーティーションへ中間結果を送出するバッチアーティファクトを指定します。このバッチアーティファクトはタスクステップの終端とチャンクステップの各チェックポイント通過後に中間結果を送出します。このバッチアーティファクトPartitionCollectorインターフェースを実装します。
  • 一つのanalyzer要素(optional)。この要素はcollectorパーティションインスタンスからの中間結果を分析するバッチアーティファクトを指定します。このバッチアーティファクトPartitionAnalyzerインターフェースを実装します。 以下はplan要素を使用したパーティーションステップの例です。
<step id="stepE" next="stepF">
  <chunk>
    <reader ...></reader>
    <processor ...></processor>
    <writer ...></writer>
  </chunk>
  <partition>
    <plan partitions="2" threads="2">
      <properties partition="0">
        <property name="firstItem" value="0"/>
        <property name="lastItem" value="500"/>
      </properties>
      <properties partition="1">
        <property name="firstItem" value="501"/>
        <property name="lastItem" value="999"/>
      </properties>
    </plan>
  </partition>
  <reducer ref="MyPartitionReducerImpl"/>
  <collector ref="MyPartitionCollectorImpl"/>
  <analyzer ref="MyPartitionAnalyzerImpl"/>
</step>

この例では、plan要素はジョブ定義ファイルで各パーティーションごとのプロパティを指定しています。

以下の例はplan要素の代わりにmapperを使用しています。PartitionMapperの実装がジョブ定義ファイルでplan要素が提供する情報と同様のことを動的に実現しています。

<step id="stepE" next="stepF">
  <chunk>
    <reader ...></reader>
    <processor ...></processor>
    <writer ...></writer>
  </chunk>
  <partition>
    <mapper ref="MyPartitionMapperImpl"/>
    <reducer ref="MyPartitionReducerImpl"/>
    <collector ref="MyPartitionCollectorImpl"/>
    <analyzer ref="MyPartitionAnalyzerImpl"/>
  </partition>
</step>

PartitionMapperインターフェースの実装例はThe phonebilling Example Applicationを参照してください。

55.4.3 フロー要素(The flow Element)

flow要素は、jobflowsplitの子要素にできます。属性はidnextです。フローは、フロー・ステップ・スプリット・条件分岐に遷移できます。flow要素は以下の要素を含むことができます。

  • 一つ以上のstep要素。
  • 一つ以上のflow要素 (optional)。
  • 一つ以上のsplit要素 (optional)。
  • 一つ以上のdecision要素 (optional)。

フロー内の最終stepnext属性もしくはnext要素を持てません。ステップとフロー内の他の要素はフロー外の要素へは遷移できません。

以下がflow要素の例です。

<flow id="flowA" next="stepE">
  <step id="flowAstepA" next="flowAstepB">...</step>
  <step id="flowAstepB" next="flowAflowC">...</step>
  <flow id="flowAflowC" next="flowAsplitD">...</flow>
  <split id="flowAsplitD" next="flowAstepE">...</split>
  <step id="flowAstepE">...</step>
</flow>

この例のフローは、三つのステップ・一つのフロー・一つのスプリットから構成されています。最終ステップはnext属性を持ちません。最終ステップが完了したとき、このフローはstepEへと遷移します。

55.4.4 スプリット要素(The split Element)

split要素は、jobflowの子要素にできます。属性はidnextです。スプリットは、スプリット・ステップ・フロー・条件分岐に遷移できます。split要素は一つ以上のflow要素のみ持つことが出来ます。そのflow要素はスプリット内の他のflow要素へのみ遷移できます。

以下は三つのフローがコンカレントに実行されるスプリットの例です。

<split id="splitA" next="stepB">
  <flow id="splitAflowA">...</flow>
  <flow id="splitAflowB">...</flow>
  <flow id="splitAflowC">...</flow>
</split>

55.4.5 条件分岐要素(The decision Element)

decision要素は、jobflowの子要素にできます。属性はidnextです。ステップ・フロー・スプリットはdecision要素に遷移できます。この要素は以前のステップ・フロー・スプリットの実行情報に基づいて次のステップ・フロー・スプリットを決定するためのバッチアーティファクトを指定します。バッチアーティファクトDeciderインタフェースを実装します。decision要素は以下の要素を含められます。

  • 一つ以上のend要素(optional)。
    この要素はバッチステータスをCOMPLETEDに設定します。
  • 一つ以上のstop要素(optional)。
    この要素はバッチステータスをSTOPPEDに設定します。
  • 一つ以上のfail要素(optional)。
    この要素はバッチステータスをFAILEDに設定します。
  • 一つ以上のnext要素(optional)。
  • 一つのproperties要素(optional)。

以下はdecider要素の例です。

<decision id="decisionA" ref="MyDeciderImpl">
  <fail on="FAILED" exit-status="FAILED_AT_DECIDER"/>
  <end on="COMPLETED" exit-status="COMPLETED_AT_DECIDER"/>
  <stop on="MY_TEMP_ISSUE_EXIST_STATUS" restart="step2"/>
</decision>

55.5 バッチアーティファクトの作成(Creating Batch Artifacts)

ジョブ定義言語(JSL)を使用してバッチアーティファクト単位でジョブを定義し終わったら、javax.batch.apiとそのサブパッケージのインターフェースを実装するJavaクラスとしてアーティファクトを作成します。

この節は主なバッチアーティファクトのインタフェースをリストアップし、バッチランタイムからコンテキストオブジェクトにアクセスする方法と、いくつかの例を紹介します。

55.5.1 バッチアーティファクトのインターフェース(Batch Artifact Interfaces)

以下の表はバッチアーティファクトを作成するために実装する必要のあるインターフェースの一覧です。インターフェースの実装はジョブ定義言語の使用で説明した要素から参照します。

Table 55-3はチャンクステップ・タスクステップ・条件分岐のバッチアーティファクトが実装するインターフェースの一覧です。

Table 55-4はパーティーションステップのバッチアーティファクトが実装するインターフェースの一覧です。

Table 55-5はジョブとステップリスナーのバッチアーティファクトが実装するインターフェースの一覧です。

Table 55-3 主なバッチアーティファクトのインターフェース(Main Batch Artifact Interfaces)

パッケージ インターフェース 説明
javax.batch.api Batchlet タスク指向ステップのビジネスロジックを実装します。batchlet要素で参照します。
javax.batch.api Decider 以前のステップ・フロー・スプリットの実行情報に基づいて次のステップ・フロー・スプリットを決定します。decision要素で参照します。
javax.batch.api.chunk CheckPointAlgorithm チャンクステップ用のカスタマイズしたチェックポイントポリシーを実装します。chunk要素内のcheckpoint-algorithm要素で参照します。
javax.batch.api.chunk ItemReader チャンクステップで入力ソースからアイテムを読み込みます。chunk要素内のreader要素から参照します。
javax.batch.api.chunk ItemProcessor チャンクステップで出力を得るために入力アイテムを処理します。chunk要素内のprocessor要素から参照します。
javax.batch.api.chunk ItemWriter チャンクステップでアイテムを出力するアイテムを書き込みます。chunk要素内のwriter要素から参照します。

Table 55-4 パーティーションのバッチアーティファクトのインタフェース(Partition Batch Artifact Interfaces)

パッケージ インターフェース 説明
javax.batch.api.partition PartitionPlan パーティーション数・スレッド数・パーティーションごとのパラメータなど、パーティーションステップの実行方法を提供します。このアーティファクトはジョブ定義ファイルから直接参照されません。
javax.batch.api.partition PartitionMapper PartitionPlanオブジェクトを提供します。partition要素内のmapper要素から参照します。
javax.batch.api.partition PartitionReducer パーティーションステップが開始・終了・ロールバックしたときのコントロールを行います。partition要素内のreducer要素から参照します。
javax.batch.api.partition PartitionCollector 中間結果を各パーティーションからanalyzerパーティーションへ送ります。partition要素内のcollector要素から参照します。
javax.batch.api.partition PartitionAnalyzer パーティションからの最終処理結果を処理します。partition要素内のanalyzer要素から参照します。

Table 55-5 リスナーのバッチアーティファクトのインタフェース(Listener Batch Artifact Interfaces)

パッケージ インターフェース 説明
javax.batch.api.listener JobListener ジョブの実行前後でインターセプトを実行します。job要素内のlistener要素から参照します。
javax.batch.api.listener StepListener ステップの実行前後でインターセプトを実行します。step要素内のlistener要素から参照します。
javax.batch.api.chunk.listener ChunkListener 各チャンクステップの実行前後およびエラー時にインターセプトを実行します。step要素内のlistener要素から参照します。
javax.batch.api.chunk.listener ItemReadListener 各アイテム読み込みの実行前後およびエラー時にインターセプトを実行します。step要素内のlistener要素から参照します。
javax.batch.api.chunk.listener ItemProcessListener 各アイテム処理の実行前後およびエラー時にインターセプトを実行します。step要素内のlistener要素から参照します。
javax.batch.api.chunk.listener ItemWriteListener 各アイテム書き込みの実行前後およびエラー時にインターセプトを実行します。step要素内のlistener要素から参照します。
javax.batch.api.chunk.listener RetryReadListener エラー発生時のアイテム読み込みリトライ時にインターセプトを実行します。step要素内のlistener要素から参照します。
javax.batch.api.chunk.listener RetryProcessListener エラー発生時のアイテム処理リトライ時にインターセプトを実行します。step要素内のlistener要素から参照します。
javax.batch.api.chunk.listener RetryWriteListener エラー発生時のアイテム書き込みリトライ時にインターセプトを実行します。step要素内のlistener要素から参照します。
javax.batch.api.chunk.listener SkipReadListener アイテム読み込みクラスでのスキップ可能例外のハンドリングをインターセプトします。step要素内のlistener要素から参照します。
javax.batch.api.chunk.listener SkipProcessListener アイテム処理クラスでのスキップ可能例外のハンドリングをインターセプトします。step要素内のlistener要素から参照します。
javax.batch.api.chunk.listener SkipWriteListener アイテム書き込みクラスでのスキップ可能例外のハンドリングをインターセプトします。step要素内のlistener要素から参照します。

55.5.2 バッチアーティファクトのDI(Dependency Injection in Batch Artifacts)

作成したバッチアーティファクトCDIで動かすための手順を以下に示します。

  • 作成したバッチアーティファクトNamedアノテーションを付与してCDI管理Beanを定義します。
    たとえば、チャンクステップでのアイテム読み込みクラスを下記のように定義します。
@Named("MyItemReaderImpl")
public class MyItemReaderImpl implements ItemReader {
    /* ... ItemReaderインタフェースのメソッドをオーバーライドする。 ... */
}
public MyItemReaderImpl() {}
<step id="stepA" next="stepB">
  <chunk>
    <reader ref="MyItemReaderImpl"></reader>
    ...
  </chunk>
</step>
@Dependent
@Named("MyItemReaderImpl")
public class MyItemReaderImpl implements ItemReader { ... }

Beanアーカイブの詳細な情報については、Chapter 25, "Contexts and Dependency Injection for Java EE: Advanced Topics".Packaging CDI Applicationsを参照してください。

Note:CDIはバッチアーティファクトでバッチランタイムからコンテキストオブジェクトを取得するために必要です。

以上の手順に従わない場合は下記のようなエラーが発生します。

55.5.3 バッチランタイムのコンテキストオブジェクトを使用する(Using the Context Objects from the Batch Runtime)

バッチランタイムはjavax.batch.runtime.contextパッケージのJobContextStepContextインターフェースを実装するコンテキストオブジェクトを提供します。これらのオブジェクトは現在のジョブとステップに関連付けされ、下記の操作が可能です。

  • 現在のジョブやステップ・その名前・インスタンスID・実行ID・バッチステータス・完了ステータスの取得。
  • ユーザー定義完了ステータスの設定。
  • ユーザーデータの保存。
  • ジョブやステップのプロパティの取得。

reader, processor, writer, batchlet, listenerなどのバッチアーティファクト実装でバッチランタイムからコンテキストオブジェクトをDIできます。以下の例は、reader実装でジョブ定義ファイルのプロパティを取得する方法を示しています。

@Dependent
@Named("MyItemReaderImpl")
public class MyItemReaderImpl implements ItemReader {
    @Inject
    JobContext jobCtx;

    public MyItemReaderImpl() {}

    @Override
    public void open(Serializable checkpoint) throws Exception {
        String fileName = jobCtx.getProperties()
                                .getProperty("log_file_name");
        ...
    }
    ...
}

DIを使用するためのバッチアーティファクトの設定方法についてはDependency Injection in Batch Artifactsを参照してください。

Noteアーティファクトコンストラクタでバッチコンテキストオブジェクトを取得してはいけません
バッチランタイムへジョブをサブミットするまでジョブは起動しないので、CDIがアプリケーション上でアーティファクトインスタンス化するときバッチコンテキストオブジェクトは利用不可です。アーティファクトインスタンス化に失敗し、アプリケーションがジョブをサブミットしてもバッチランタイムはバッチアーティファクトを見つけられません。

55.6 バッチランタイムへのジョブのサブミット(Submitting Jobs to the Batch Runtime)

javax.batch.operationsパッケージのJobOperatorインターフェースを使用してジョブのサブミットと情報取得をすることができます。このインタフェースは以下の機能を提供します。

  • すべてのジョブ名の取得
  • ジョブの開始・停止・再開・放棄(abandon)
  • ジョブインスタンスの取得とジョブの実行

javax.batch.runtimeパッケージのBatchRuntimeクラスはJobOperatorオブジェクトを取得するためのgetJobOperatorファクトリーメソッドを提供します。

55.6.1 ジョブの開始(Starting a Job)

以下のコード例はJobOperatorオブジェクトを取得してバッチジョブをサブミットする方法を示しています。

JobOperator jobOperator = BatchRuntime.getJobOperator();
Properties props = new Properties();
props.setProperty("parameter1", "value1");
...
long execID = jobOperator.start("simplejob", props);

JobOperator.startメソッドの引数の一つ目はジョブ定義ファイルで指定したジョブ名です。二つ目は実行ジョブのパラメーターであるPropertiesオブジェクトです。ジョブ実行時にのみ判明するパラメータについてはこのプロパティ引数を使用して渡せます。

55.6.2 ジョブステータスの確認(Checking the Status of a Job)

javax.batch.runtimeパッケージのJobExecutionインターフェースはサブミットされたジョブの情報を取得するメソッドを提供します。

  • バッチと実行ジョブの完了ステータスの取得
  • 実行開始・更新・終了時刻の取得
  • ジョブ名の取得
  • 実行IDの取得

以下のコード例は実行IDを使用してジョブのバッチステータスを取得する方法を示しています。

JobExecution jobExec = jobOperator.getJobExecution(execID);
String status = jobExec.getBatchStatus().toString();

55.6.3 アプリケーションにおけるバッチランタイムの起動(Invoking the Batch Runtime in Your Application)

バッチランタイムを起動するコンポーネントは、特定のアプリケーションのアーキテクチャに依存します。たとえば、EJBサーブレット・マネージドBeanなどからバッチランタイムを起動できます。

JSFのマネージドBeanからバッチランタイムを起動する方法の詳細はThe webserverlog Example ApplicationThe phonebilling Example Applicationを参照してください。

55.7 バッチアプリケーションのパッケージ作成(Packaging Batch Applications)

ジョブ定義ファイルとバッチアーティファクトは異なるパッケージに分割出来ず*7、任意のJava EEアプリケーションに含めることが出来ます。

アプリケーションのクラスにバッチアーティファクトのクラスと、ジョブ定義ファイルを以下のディレクトリに配置して、パッケージを作成します。

  • jarの場合はMETA-INF/batch-jobs/
  • warの場合はWEB-INF/classes/META-INF/batch-jobs/

ジョブ定義ファイルの名前はジョブIDと一致する必要があります。たとえば、以下のようにジョブを定義した場合、

<?xml version="1.0" encoding="UTF-8"?>
<job id="simplejob" xmlns="http://xmlns.jcp.org/xml/ns/javaee" 
                    version="1.0">
...
</job>

ジョブ定義ファイルの名前をWEB-INF/classes/META-INF/batch-jobs/simplejob.xmlにして、warアーカイブを作成します。

55.8 The webserverlog Example Application

tut-install/examples/batch/webserverlog/*8からダウンロードできるwebserverlogサンプルアプリケーションは、Java EEバッチフレームワークでwebサーバーログを分析する方法を紹介しています。このサンプルアプリケーションはログファイルを読み込み、タブレット端末からのPVの何パーセントが売上となったか、を調査します。

55.8.1 Architecture of the webserverlog Example Application

webserverlogサンプルアプリケーションは以下の要素から構成されます。

  • ジョブ定義言語(JSL)で記述したジョブ定義ファイル(webserverlog.xml)にチャンクステップとタスクステップからなるバッチジョブを定義。
  • バッチジョブの入力となるログファイル(log1.txt)。
  • チャンクステップの入力・出力アイテムを表現する二つのJavaクラス(LogLineLogFilteredLine)。
  • チャンクステップの実装となる三つのバッチアーティファクト(LogLineReader, LogLineProcessor, LogFilteredLineWriter)。このステップはwebサーバーログを読み込み、クライアントの使用したブラウザでフィルタし、結果をテキストファイルに書き込み、を行います。
  • リスナーの単純な実装となる二つのバッチアーティファクト(InfoJobListenerInfoItemProcessListener)。
  • バッチアーティファクト(MobileBatchlet.java)はフィルタされたログを基に統計データを計算します。
  • バッチアプリケーションのフロントエンドとなる二つのJSFページ(index.xhtmljobstarted.xhtml)。前者のページはバッチジョブで処理予定のログファイルを表示し、後者のページはジョブのステータスと結果を表示します。
  • JSFページから使用されるセッションBean(JsfBean)。バッチランタイムへジョブをサブミットし、ジョブのステータスをチェックし、最後にテキストファイルから結果を読み込みます。

55.8.1.1 The Job Definition File

ジョブ定義ファイルwebserverlog.xmlWEB-INF/classes/META-INF/batch-jobs/ディレクトリに配置されており、七つのジョブレベルのプロパティと二つのステップを持ちます。

<?xml version="1.0" encoding="UTF-8"?>
<job id="webserverlog" xmlns="http://xmlns.jcp.org/xml/ns/javaee" 
                       version="1.0">
    <properties>
        <property name="log_file_name" value="log1.txt"/>
        <property name="filtered_file_name" value="filtered1.txt"/>
        <property name="num_browsers" value="2"/>
        <property name="browser_1" value="Tablet Browser D"/>
        <property name="browser_2" value="Tablet Browser E"/>
        <property name="buy_page" value="/auth/buy.html"/>
        <property name="out_file_name" value="result1.txt"/>
    </properties>
    <listeners>
        <listener ref="InfoJobListener"/>
    </listeners>
    <step id="mobilefilter" next="mobileanalyzer"> ... </step>
    <step id="mobileanalyzer"> ... </step>
</job>

最初のステップは以下のように定義されています。

<step id="mobilefilter" next="mobileanalyzer">
    <listeners>
        <listener ref="InfoItemProcessListeners"/>
    </listeners>
    <chunk checkpoint-policy="item" item-count="10">
        <reader ref="LogLineReader"></reader>
        <processor ref="LogLineProcessor"></processor>
        <writer ref="LogFilteredLineWriter"></writer>
    </chunk>
</step>

このステップは通常のチャンクステップで、ステップの各フェーズを実装するバッチアーティファクトが指定されています。バッチアーティファクトの参照は完全修飾クラス名(FQDN)ではなく、@NamedアノテーションをつけられたCDI Beanで指定されています。 次のステップは以下のように定義されています。

<step id="mobileanalyzer">
    <batchlet ref="MobileBatchlet"></batchlet>
    <end on="COMPLETED"/>
</step>

ステップには、タスクステップを実装するバッチアーティファクトが指定されています。これがジョブの最終ステップです。

55.8.1.2 The LogLine and LogFilteredLine Items

LogLineクラスはwebサーバーログファイルのエントリを表現しており、以下のように定義されています。

public class LogLine {
    private String datetime;
    private String ipaddr;
    private String browser;
    private String url;

    /* ... Constructor, getters, and setters ... */
}

LogFileteredLineクラスはこのクラスと似ているが、クライアントIPアドレスとURLの二つのフィールドのみ持ちます。

55.8.1.3 The Chunk Step Batch Artifacts

最初のステップは三つのバッチアーティファクトLogLineReaderLogLineProcessorLogFilteredLineWriterで構成されています。

LogLineReaderアーティファクトはwebサーバーログを読み込みます。

@Dependent
@Named("LogLineReader")
public class LogLineReader implements ItemReader {
    private ItemNumberCheckpoint checkpoint;
    private String fileName;
    private BufferedReader breader;
    @Inject
    private JobContext jobCtx;
    
    public LogLineReader() { }

    /* ... Override the open, close, readItem, and 
     *     checkpointInfo methods ... */
}

openメソッドlog_file_nameプロパティを参照し、BufferedReaderでログファイルをオープンします。この例でのログファイルはアプリケーションのディレクトリwebserverlog/WEB-INF/classes/log1.txtに含まれています。

fileName = jobCtx.getProperties().getProperty("log_file_name");
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
InputStream iStream = classLoader.getResourceAsStream(fileName);
breader = new BufferedReader(new InputStreamReader(iStream));

チェックポイントオブジェクトが存在する場合、openメソッドは最終チェックポイントまで読み込み位置を前進させます。存在しなければ、チェックポイントオブジェクトをnewします。チェックポイントオブジェクトは最後にコミットされたチャンクの行ナンバーを記録し続けています。

readメソッドは、newしたLogLineを返すか、ログファイルのEOFに達したらnullを返します。

@Override
public Object readItem() throws Exception {
    String entry = breader.readLine();
    if (entry != null) {
        checkpoint.nextLine();
        return new LogLine(entry);
    } else
        return null;
}

LogLineProcessorアーティファクトはジョブのプロパティからブラウザのリストを取得し、そのリストに従ってログエントリをフィルタします。

@Override
public Object processItem(Object item) {
    /* Obtain a list of browsers we are interested in */
    if (nbrowsers == 0) {
        Properties props = jobCtx.getProperties();
        nbrowsers = Integer.parseInt(props.getProperty("num_browsers"));
        browsers = new String[nbrowsers];
        for (int i = 1; i<nbrowsers+1; i++)
            browsers[i-1] = props.getProperty("browser_" + i);
    }
    
    LogLine logline = (LogLine) item;
    /* Filter for only the mobile/tablet browsers as specified */
    for (int i=0; i<nbrowsers; i++) {
        if (logline.getBrowser().equals(browsers[i])) {
            return new LogFilteredLine(logline);
        }
    }
    return null;
}

LogFilteredLineWriterアーティファクトはジョブのプロパティから出力ファイル名を取得します。openメソッドは書き込みのためにファイルをオープンします。もしチェックポイントオブジェクトが存在する場合、アーティファクトはファイルのEOFから書き込みを続行します。そうでない場合、出力ファイルが存在していれば上書きします。writeItemsメソッドはフィルタされたアイテムを出力ファイルへ書き込みます。

@Override
public void writeItems(List<Object> items) throws Exception {
    /* Write the filtered lines to the output file */
    for (int i = 0; i < items.size(); i++) {
        LogFilteredLine filtLine = (LogFilteredLine) items.get(i);
        bwriter.write(filtLine.toString());
        bwriter.newLine();
    }
}

55.8.1.4 The Listener Batch Artifacts

InfoJobListenerバッチアーティファクトは単純なリスナで、ジョブの開始・終了ログを出力します。

@Dependent
@Named("InfoJobListener")
public class InfoJobListener implements JobListener {
    ...
    @Override
    public void beforeJob() throws Exception {
        logger.log(Level.INFO, "The job is starting");
    }
 
    @Override
    public void afterJob() throws Exception { ... }
}

InfoItemProcessListenerバッチアーティファクトはチャンクステップ向けのインタフェースItemProcessListenerを実装します。

@Dependent
@Named("InfoItemProcessListener")
public class InfoItemProcessListener implements ItemProcessListener {
    ...
    @Override
    public void beforeProcess(Object o) throws Exception {
        LogLine logline = (LogLine) o;
        logger.log(Level.INFO, "Processing entry " + logline);
    }
    ...
}

55.8.1.5 The Task Step Batch Artifact

タスクステップの実装はMobileBatchletアーティファクトで、フィルタされたログエントリの何バーセントが売上になったを計算します。

@Override
public String process() throws Exception {
    /* ... Get properties from the job definition file ... */
    /* Count from the output of the previous chunk step */
    breader = new BufferedReader(new FileReader(fileName));
    String line = breader.readLine();
    while (line != null) {
        String[] lineSplit = line.split(", ");
        if (buyPage.compareTo(lineSplit[1]) == 0)
            pageVisits++;
        totalVisits++;
        line = breader.readLine();
    }
    breader.close();
    /* ... Write the result ... */
}

55.8.1.6 The JavaServer Faces Pages

index.xhtmlページはwebサーバーログを表示するテキストエリアで構成されています。このページのボタンはバッチジョブをサブミットして次のページへ遷移します。

<body>
    ...
    <textarea cols="90" rows="25" 
        readonly="true">#{jsfBean.getInputLog()}</textarea>
    ...
    <h:form>
        <h:commandButton value="Start Batch Job" 
            action="#{jsfBean.startBatchJob()}"></h:commandButton>
    </h:form>
</body>

このページはセッションBeanのログファイルを表示するメソッドとバッチジョブをサブミットするメソッドを呼び出します。

jobstarted.xhtmlページはバッチジョブの現在のステータスをチェックするボタンとジョブが終了したときの結果表示を行います。

<p>Current Status of the Job: <b>#{jsfBean.jobStatus}</b></p>
<p>#{jsfBean.showResults()}</p>
<h:form>
    <h:commandButton value="Check Status" action="jobstarted"
                     rendered="#{jsfBean.completed==false}">
    </h:commandButton>
</h:form>

55.8.1.7 The Session Bean

JsfBeanセッションBeanはバッチランタイムへジョブをサブミットし、ジョブのステータスをチェックし、テキストファイルから結果を読み込みます。

startBatchJobメソッドはバッチランタイムへジョブをサブミットします。

/* Submit the batch job to the batch runtime.
 * JSF Navigation method (return the name of the next page) */
public String startBatchJob() {
    jobOperator = BatchRuntime.getJobOperator();
    execID = jobOperator.start("webserverlog", null);
    return "jobstarted";
}

getJobStatusメソッドはジョブステータスをチェックします。

/* Get the status of the job from the batch runtime */
public String getJobStatus() {
    return jobOperator.getJobExecution(execID).getBatchStatus()
                                              .toString();
}

showResultsメソッドはテキストファイルから結果を読み込みます。

55.8.2 Running the webserverlog Example Application

webserverlogサンプルアプリケーションのNetBeansコマンドラインからの起動方法について説明します。

55.8.2.1 To Run the webserverlog Example Application Using NetBeans IDE

  1. Fileメニューから、Open Projectを選択する。
  2. Open Projectダイアログボックスでサンプルアプリケーションのディレクトリに移動する。
    tut-install/examples/batch/webserverlog
  3. webserverlogディレクトリを選択する。
  4. Open Projectをクリックする。
  5. Projectsタブで、webserverlogプロジェクトを右クリックしてRunを選択する。
    このコマンドは、ビルドしてwebserverlog.warを作成し、target/ディレクトリに配置し、サーバへデプロイし、webブラウザを立ち上げて、以下のURLを表示します。
    http://localhost:8080/webserverlog/

55.8.2.2 To Run the webserverlog Example Application Using Maven

  1. GlassFishサーバが起動済みなことを確認してください。詳細な情報はChapter 2, "Using the Tutorial Examples"を確認してください。
  2. ターミナルで下記ディレクトリに移動する。
    tut-install/examples/batch/webserverlog
  3. アプリケーションをデプロイするために下記コマンドを入力する。
    mvn install
  4. webブラウザを起動して下記のアドレスを入力する。
    http://localhost:8080/webserverlog/

55.8 The webserverlog Example Application

tut-install/examples/batch/webserverlog/からダウンロードできるwebserverlogサンプルアプリケーションは、Java EEバッチフレームワークでwebサーバーログを分析する方法を紹介しています。このサンプルアプリケーションはログファイルを読み込み、タブレット端末からのPVの何パーセントが売上となったか、を調査します。

55.8.1 Architecture of the webserverlog Example Application

webserverlogサンプルアプリケーションは以下の要素から構成されます。

  • ジョブ定義言語(JSL)で記述したジョブ定義ファイル(webserverlog.xml)にチャンクステップとタスクステップからなるバッチジョブを定義。
  • バッチジョブの入力となるログファイル(log1.txt)。
  • チャンクステップの入力・出力アイテムを表現する二つのJavaクラス(LogLineLogFilteredLine)。
  • チャンクステップの実装となる三つのバッチアーティファクト(LogLineReader, LogLineProcessor, LogFilteredLineWriter)。このステップはwebサーバーログを読み込み、クライアントの使用したブラウザでフィルタし、結果をテキストファイルに書き込み、を行います。
  • リスナーの単純な実装となる二つのバッチアーティファクト(InfoJobListenerInfoItemProcessListener)。
  • バッチアーティファクト(MobileBatchlet.java)はフィルタされたログを基に統計データを計算します。
  • バッチアプリケーションのフロントエンドとなる二つのJSFページ(index.xhtmljobstarted.xhtml)。前者のページはバッチジョブで処理予定のログファイルを表示し、後者のページはジョブのステータスと結果を表示します。
  • JSFページから使用されるセッションBean(JsfBean)。バッチランタイムへジョブをサブミットし、ジョブのステータスをチェックし、最後にテキストファイルから結果を読み込みます。

55.8.1.1 The Job Definition File

ジョブ定義ファイルwebserverlog.xmlWEB-INF/classes/META-INF/batch-jobs/ディレクトリに配置されており、七つのジョブレベルのプロパティと二つのステップを持ちます。

<?xml version="1.0" encoding="UTF-8"?>
<job id="webserverlog" xmlns="http://xmlns.jcp.org/xml/ns/javaee" 
                       version="1.0">
    <properties>
        <property name="log_file_name" value="log1.txt"/>
        <property name="filtered_file_name" value="filtered1.txt"/>
        <property name="num_browsers" value="2"/>
        <property name="browser_1" value="Tablet Browser D"/>
        <property name="browser_2" value="Tablet Browser E"/>
        <property name="buy_page" value="/auth/buy.html"/>
        <property name="out_file_name" value="result1.txt"/>
    </properties>
    <listeners>
        <listener ref="InfoJobListener"/>
    </listeners>
    <step id="mobilefilter" next="mobileanalyzer"> ... </step>
    <step id="mobileanalyzer"> ... </step>
</job>

最初のステップは以下のように定義されています。

<step id="mobilefilter" next="mobileanalyzer">
    <listeners>
        <listener ref="InfoItemProcessListeners"/>
    </listeners>
    <chunk checkpoint-policy="item" item-count="10">
        <reader ref="LogLineReader"></reader>
        <processor ref="LogLineProcessor"></processor>
        <writer ref="LogFilteredLineWriter"></writer>
    </chunk>
</step>

このステップは通常のチャンクステップで、ステップの各フェーズを実装するバッチアーティファクトが指定されています。バッチアーティファクトの参照は完全修飾クラス名(FQDN)ではなく、@NamedアノテーションをつけられたCDI Beanで指定されています。 次のステップは以下のように定義されています。

<step id="mobileanalyzer">
    <batchlet ref="MobileBatchlet"></batchlet>
    <end on="COMPLETED"/>
</step>

ステップには、タスクステップを実装するバッチアーティファクトが指定されています。これがジョブの最終ステップです。

55.8.1.2 The LogLine and LogFilteredLine Items

LogLineクラスはwebサーバーログファイルのエントリを表現しており、以下のように定義されています。

public class LogLine {
    private String datetime;
    private String ipaddr;
    private String browser;
    private String url;

    /* ... Constructor, getters, and setters ... */
}

LogFileteredLineクラスはこのクラスと似ているが、クライアントIPアドレスとURLの二つのフィールドのみ持ちます。

55.8.1.3 The Chunk Step Batch Artifacts

最初のステップは三つのバッチアーティファクトLogLineReaderLogLineProcessorLogFilteredLineWriterで構成されています。

LogLineReaderアーティファクトはwebサーバーログを読み込みます。

@Dependent
@Named("LogLineReader")
public class LogLineReader implements ItemReader {
    private ItemNumberCheckpoint checkpoint;
    private String fileName;
    private BufferedReader breader;
    @Inject
    private JobContext jobCtx;
    
    public LogLineReader() { }

    /* ... Override the open, close, readItem, and 
     *     checkpointInfo methods ... */
}

openメソッドlog_file_nameプロパティを参照し、BufferedReaderでログファイルをオープンします。この例でのログファイルはアプリケーションのディレクトリwebserverlog/WEB-INF/classes/log1.txtに含まれています。

fileName = jobCtx.getProperties().getProperty("log_file_name");
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
InputStream iStream = classLoader.getResourceAsStream(fileName);
breader = new BufferedReader(new InputStreamReader(iStream));

チェックポイントオブジェクトが存在する場合、openメソッドは最終チェックポイントまで読み込み位置を前進させます。存在しなければ、チェックポイントオブジェクトをnewします。チェックポイントオブジェクトは最後にコミットされたチャンクの行ナンバーを記録し続けています。

readメソッドは、newしたLogLineを返すか、ログファイルのEOFに達したらnullを返します。

@Override
public Object readItem() throws Exception {
    String entry = breader.readLine();
    if (entry != null) {
        checkpoint.nextLine();
        return new LogLine(entry);
    } else
        return null;
}

LogLineProcessorアーティファクトはジョブのプロパティからブラウザのリストを取得し、そのリストに従ってログエントリをフィルタします。

@Override
public Object processItem(Object item) {
    /* Obtain a list of browsers we are interested in */
    if (nbrowsers == 0) {
        Properties props = jobCtx.getProperties();
        nbrowsers = Integer.parseInt(props.getProperty("num_browsers"));
        browsers = new String[nbrowsers];
        for (int i = 1; i<nbrowsers+1; i++)
            browsers[i-1] = props.getProperty("browser_" + i);
    }
    
    LogLine logline = (LogLine) item;
    /* Filter for only the mobile/tablet browsers as specified */
    for (int i=0; i<nbrowsers; i++) {
        if (logline.getBrowser().equals(browsers[i])) {
            return new LogFilteredLine(logline);
        }
    }
    return null;
}

LogFilteredLineWriterアーティファクトはジョブのプロパティから出力ファイル名を取得します。openメソッドは書き込みのためにファイルをオープンします。もしチェックポイントオブジェクトが存在する場合、アーティファクトはファイルのEOFから書き込みを続行します。そうでない場合、出力ファイルが存在していれば上書きします。writeItemsメソッドはフィルタされたアイテムを出力ファイルへ書き込みます。

@Override
public void writeItems(List<Object> items) throws Exception {
    /* Write the filtered lines to the output file */
    for (int i = 0; i < items.size(); i++) {
        LogFilteredLine filtLine = (LogFilteredLine) items.get(i);
        bwriter.write(filtLine.toString());
        bwriter.newLine();
    }
}

55.8.1.4 The Listener Batch Artifacts

InfoJobListenerバッチアーティファクトは単純なリスナで、ジョブの開始・終了ログを出力します。

@Dependent
@Named("InfoJobListener")
public class InfoJobListener implements JobListener {
    ...
    @Override
    public void beforeJob() throws Exception {
        logger.log(Level.INFO, "The job is starting");
    }
 
    @Override
    public void afterJob() throws Exception { ... }
}

InfoItemProcessListenerバッチアーティファクトはチャンクステップ向けのインタフェースItemProcessListenerを実装します。

@Dependent
@Named("InfoItemProcessListener")
public class InfoItemProcessListener implements ItemProcessListener {
    ...
    @Override
    public void beforeProcess(Object o) throws Exception {
        LogLine logline = (LogLine) o;
        logger.log(Level.INFO, "Processing entry " + logline);
    }
    ...
}

55.8.1.5 The Task Step Batch Artifact

タスクステップの実装はMobileBatchletアーティファクトで、フィルタされたログエントリの何バーセントが売上になったを計算します。

@Override
public String process() throws Exception {
    /* ... Get properties from the job definition file ... */
    /* Count from the output of the previous chunk step */
    breader = new BufferedReader(new FileReader(fileName));
    String line = breader.readLine();
    while (line != null) {
        String[] lineSplit = line.split(", ");
        if (buyPage.compareTo(lineSplit[1]) == 0)
            pageVisits++;
        totalVisits++;
        line = breader.readLine();
    }
    breader.close();
    /* ... Write the result ... */
}

55.8.1.6 The JavaServer Faces Pages

index.xhtmlページはwebサーバーログを表示するテキストエリアで構成されています。このページのボタンはバッチジョブをサブミットして次のページへ遷移します。

<body>
    ...
    <textarea cols="90" rows="25" 
        readonly="true">#{jsfBean.getInputLog()}</textarea>
    ...
    <h:form>
        <h:commandButton value="Start Batch Job" 
            action="#{jsfBean.startBatchJob()}"></h:commandButton>
    </h:form>
</body>

このページはセッションBeanのログファイルを表示するメソッドとバッチジョブをサブミットするメソッドを呼び出します。

jobstarted.xhtmlページはバッチジョブの現在のステータスをチェックするボタンとジョブが終了したときの結果表示を行います。

<p>Current Status of the Job: <b>#{jsfBean.jobStatus}</b></p>
<p>#{jsfBean.showResults()}</p>
<h:form>
    <h:commandButton value="Check Status" action="jobstarted"
                     rendered="#{jsfBean.completed==false}">
    </h:commandButton>
</h:form>

55.8.1.7 The Session Bean

JsfBeanセッションBeanはバッチランタイムへジョブをサブミットし、ジョブのステータスをチェックし、テキストファイルから結果を読み込みます。

startBatchJobメソッドはバッチランタイムへジョブをサブミットします。

/* Submit the batch job to the batch runtime.
 * JSF Navigation method (return the name of the next page) */
public String startBatchJob() {
    jobOperator = BatchRuntime.getJobOperator();
    execID = jobOperator.start("webserverlog", null);
    return "jobstarted";
}

getJobStatusメソッドはジョブステータスをチェックします。

/* Get the status of the job from the batch runtime */
public String getJobStatus() {
    return jobOperator.getJobExecution(execID).getBatchStatus()
                                              .toString();
}

showResultsメソッドはテキストファイルから結果を読み込みます。

55.8.2 Running the webserverlog Example Application

webserverlogサンプルアプリケーションのNetBeansコマンドラインからの起動方法について説明します。

55.8.2.1 To Run the webserverlog Example Application Using NetBeans IDE

  1. Fileメニューから、Open Projectを選択する。
  2. Open Projectダイアログボックスでサンプルアプリケーションのディレクトリに移動する。
    tut-install/examples/batch
  3. webserverlogディレクトリを選択する。
  4. Open Projectをクリックする。
  5. Projectsタブで、webserverlogプロジェクトを右クリックしてRunを選択する。
    このコマンドは、ビルドしてwebserverlog.warを作成し、target/ディレクトリに配置し、サーバへデプロイし、webブラウザを立ち上げて、以下のURLを表示します。
    http://localhost:8080/webserverlog/

55.8.2.2 To Run the webserverlog Example Application Using Maven

  1. GlassFishサーバが起動済みなことを確認してください。詳細な情報はChapter 2, "Using the Tutorial Examples"を確認してください。
  2. ターミナルで下記ディレクトリに移動する。
    tut-install/examples/batch/webserverlog
  3. アプリケーションをデプロイするために下記コマンドを入力する。
    mvn install
  4. webブラウザを起動して下記のアドレスを入力する。
    http://localhost:8080/webserverlog/

55.9 The phonebilling Example Application

tut-install/examples/batch/phonebilling/からダウンロードできるphonebillingサンプルアプリケーションは、Java EEバッチフレームワークで通話請求システムを実装する方法を紹介しています。このサンプルアプリケーションは通話ログファイルを処理して顧客ごとに請求を作成します。

55.9.1 Architecture of the phonebilling Example Application

phonebillingサンプルアプリケーションは以下の要素から構成されます。

  • ジョブ定義言語(JSL)で記述したジョブ定義ファイル(phonebilling.xml)に二つのチャンクステップからなるバッチジョブを定義。最初のステップはログファイルから通話記録を読み込んで請求と関連付けます。次のステップは請求金額を計算してテキストファイルに個々の請求を書き込みます。
  • バッチジョブのためにログファイルを生成するJavaクラス(CallRecordLogCreator)。これは補足的なコンポーネントで、この例における主要機能として紹介はされません。
  • 通話記録と請求を表現する二つのJPAエンティティ(CallRecordPhoneBill)。アプリケーションはデータベースへこれらのエンティティを保存するためにJPAのEntity Managerを使用します。
  • 二番目のステップを実装する四つのバッチアーティファクト(BillReader, BillProcessor, BillWriter, BillPartitionMapper)。このステップはパーティーションステップで、データベースから請求を取得し、請求金額を計算し、テキストファイルへ書き込みます。
  • バッチアプリケーションのフロントエンドとなる二つのJSFページ(index.xhtmljobstarted.xhtmll)。前者のページはバッチジョブで処理予定のログファイルを表示し、後者のページはジョブのステータスと顧客ごとの請求を表示します。
  • JSFページから使用されるセッションBean(JsfBean)。バッチランタイムへジョブをサブミットし、ジョブのステータスをチェックし、最後に請求のテキストファイルを読み込みます。

55.9.1.1 The Job Definition File

ジョブ定義ファイルphonebilling.xmlWEB-INF/classes/META-INF/batch-jobs/ディレクトリに配置されており、三つのジョブレベルのプロパティと二つのステップを持ちます。

<?xml version="1.0" encoding="UTF-8"?>
<job id="phonebilling" xmlns="http://xmlns.jcp.org/xml/ns/javaee" 
                       version="1.0">
    <properties>
        <property name="log_file_name" value="log1.txt"/>
        <property name="airtime_price" value="0.08"/>
        <property name="tax_rate" value="0.07"/>
    </properties>
    <step id="callrecords" next="bills"> ... </step>
    <step id="bills"> ... </step>
</job>

最初のステップは以下にように定義されています。

<step id="callrecords" next="bills">
    <chunk checkpoint-policy="item" item-count="10">
        <reader ref="CallRecordReader"></reader>
        <processor ref="CallRecordProcessor"></processor>
        <writer ref="CallRecordWriter"></writer>
    </chunk>
</step>

このステップは通常のチャンクステップで、ステップの各フェーズを実装するバッチアーティファクトが指定されています。バッチアーティファクトの参照は完全修飾クラス名(FQDN)ではなく、@NamedアノテーションをつけられたCDI Beanで指定されています。 次のステップは以下のように定義されています。

<step id="bills">
    <chunk checkpoint-policy="item" item-count="2">
        <reader ref="BillReader"></reader>
        <processor ref="BillProcessor"></processor>
        <writer ref="BillWriter"></writer>
    </chunk>
    <partition>
        <mapper ref="BillPartitionMapper"/>
    </partition>
    <end on="COMPLETED"/>
</step>

このステップはパーティーションチャンクステップです。パーティーションプランはplan要素を使用する代わりにBillPartitionMapperアーティファクトで指定されています。

55.9.1.2 The CallRecord and PhoneBill Entities

CallRecordエンティティは以下のように定義されています。

@Entity
public class CallRecord implements Serializable {
    @Id @GeneratedValue
    private Long id;
    @Temporal(TemporalType.DATE)
    private Date datetime;
    private String fromNumber;
    private String toNumber;
    private int minutes;
    private int seconds;
    private BigDecimal price;

    public CallRecord() { }

    public CallRecord(String datetime, String from, 
                      String to, int min, int sec)                       throws ParseException { ... }

    public CallRecord(String jsonData) throws ParseException { ... }

    /* ... Getters and setters ... */
}

idフィールドはデータベースからCallRecordオブジェクトの取得および保存のためにJPAによって自動的に生成されます。

二番目のコンストラクタJSON Processing APIを使用してログファイルのJSONからCallRecordオブジェクトを生成します。ログファイルのエントリは以下のようになっています。

{"datetime":"03/01/2013 04:03","from":"555-0101",
"to":"555-0114","length":"03:39"}

PhoneBillエンティティは以下のように定義されています。

@Entity
public class PhoneBill implements Serializable {
    @Id
    private String phoneNumber;
    @OneToMany(cascade = CascadeType.PERSIST)
    @OrderBy("datetime ASC")
    private List<CallRecord> calls;
    private BigDecimal amountBase;
    private BigDecimal taxRate;
    private BigDecimal tax;
    private BigDecimal amountTotal;
 
    public PhoneBill() { }
    
    public PhoneBill(String number) {
        this.phoneNumber = number;
        calls = new ArrayList<>();
    }
    
    public void addCall(CallRecord call) {
        calls.add(call);
    }
    
    public void calculate(BigDecimal taxRate) { ... }

    /* ... Getters and setters ... *
}

OneToManyアノテーションは請求と通話履歴のリレーションを定義しています。CascadeType.PERSISTパラメーターは、PhoneBillがパーシストされたときにListの要素も自動的にパーシストされるように指定しています。OrderByアノテーションはデータベースからListを取得する際の順序を定義しています。

バッチアーティファクトはこれら二つのエンティティを、読み込み・処理・書き込みに使用します。

JPAの詳細については、Chapter 37, "Introduction to the Java Persistence API"を、JSON Processing APIについては、Chapter 19, "JSON Processing"を参照してください。

55.9.1.3 The Call Records Chunk Step

最初のステップはCallRecordReader, CallRecordProcessor, CallRecordWriterのバッチアーティファクトで構成されています。

CallRecordReaderアーティファクトはログファイルから通話記録を読み込みます。

@Dependent
@Named("CallRecordReader")
public class CallRecordReader implements ItemReader {
    private ItemNumberCheckpoint checkpoint;
    private String fileName;
    private BufferedReader breader;
    @Inject
    JobContext jobCtx;

    /* ... Override the open, close, readItem, 
     *     and checkpointInfo methods ... */
}

openメソッドlog_filenameプロパティをプロパティを参照し、BufferedReaderでログファイルをオープンします。

fileName = jobCtx.getProperties().getProperty("log_file_name");
breader = new BufferedReader(new FileReader(fileName));

チェックポイントオブジェクトが存在する場合、openメソッドは最終チェックポイントまで読み込み位置を前進させます。存在しなければ、チェックポイントオブジェクトをnewします。チェックポイントオブジェクトは最後にコミットされたチャンクの行ナンバーを記録し続けています。

readメソッドは、newしたCallRecordを返すか、ログファイルのEOFに達したらnullを返します。

@Override
public Object readItem() throws Exception {
    /* Read a line from the log file and 
     * create a CallRecord from JSON */
    String callEntryJson = breader.readLine();
    if (callEntryJson != null) {
        checkpoint.nextItem();
        return new CallRecord(callEntryJson);
    } else
        return null;
}

CallRecordProcessorアーティファクトはジョブのプロパティから通話時間当たりの料金を取得し、通話ごとの料金を計算します。このアーティファクトprocessItemメソッドのみオーバーライドします。

CallRecordWriterアーティファクトは通話記録と請求を関連付けてデータベースへ保存します。このアーティファクトは、open, close, writeItems, checkpointInfoメソッドをオーバーライドします。writeItemsメソッドは以下のようになっています。

@Override
public void writeItems(List<Object> callList) throws Exception {
    
    for (Object callObject : callList) {
        CallRecord call = (CallRecord) callObject;
        PhoneBill bill = em.find(PhoneBill.class, call.getFromNumber());
        if (bill == null) {
            /* No bill for this customer yet, create one */
            bill = new PhoneBill(call.getFromNumber());
            bill.addCall(call);
            em.persist(bill);
        } else {
            /* Add call to existing bill */
            bill.addCall(call);
        }
    }
}

55.9.1.4 The Phone Billing Chunk Step

次のステップは、BillReader, BillProcessor, BillWriter, BillPartitionMapperバッチアーティファクトから構成されています。このステップはデータベースから請求を取得し、税金と合計金額を計算し、テキストファイルへ請求を書き込みます。個々の請求に対する処理は他の請求とは独立しているので、このステップはパーティションにして1つ以上のスレッドで動作させられます。

BillPartitionMapperアーティファクトはパーティーション数とパーティションごとのパラメータを指定します。この例では、パラメータは各パーティションが処理すべきアイテムの処理範囲になっています。このアーティファクトは与えられた処理範囲を計算するためにデータベースから請求を取得します。また、PartitionPlanインタフェースのgetPartitionsgetPartitionPropertiesをオーバーライドしたパーティーションのプランオブジェクトの役割も提供します。getPartitionsメソッドは以下のようになります。

@Override
public Properties[] getPartitionProperties() {
    /* Assign an (approximately) equal number of elements
     * to each partition. */
    long totalItems = getBillCount();
    long partItems = (long) totalItems / getPartitions();
    long remItems = totalItems % getPartitions();
 
    /* Populate a Properties array. Each Properties element
     * in the array corresponds to a partition. */
    Properties[] props = new Properties[getPartitions()];

    for (int i = 0; i < getPartitions(); i++) {
        props[i] = new Properties();
        props[i].put("firstItem", i * partItems);
        /* Last partition gets the remainder elements */
        if (i == getPartitions() - 1) {
            props[i].put("numItems", partItems + remItems);
        } else {
            props[i].put("numItems", partItems);
        }
    } 
    return props;
}

BillReaderアーティファクトはパーティーションのパラメータを以下のように取得します。

@Dependent
@Named("BillReader")
public class BillReader implements ItemReader {
    ...
    @Inject
    JobContext jobCtx;
    private Properties partParams;
    ...
    @Override
    public void open(Serializable ckpt) throws Exception {
        /* Get the parameters for this partition */
        JobOperator jobOperator = BatchRuntime.getJobOperator();
        long execID = jobCtx.getExecutionId();
        partParams = jobOperator.getParameters(execID);

        /* Get the range of items to work on in this partition */
        long firstItem0 = ((Long) partParams.get("firstItem")).longValue();
        long numItems0 = ((Long) partParams.get("numItems")).longValue();
        ...
    }
    ...
}

このアーティファクトはまたJPAエンティティマネージャからデータを読み込むためのイテレータも取得します。

/* Obtain an iterator for the bills in this partition */
String query = "SELECT b FROM PhoneBill b ORDER BY b.phoneNumber";
Query q = em.createQuery(query)
        .setFirstResult((int)firstItem).setMaxResults((int)numItems);
iterator = q.getResultList().iterator();

BillProcessorアーティファクトは通話記録のリストを走査し、請求ごとに税金と合計金額を計算します。

BillWriterアーティファクトはテキストファイルに請求を書き込みます。

55.9.1.5 The JavaServer Faces Pages

index.xhtmlは通話記録のログファイルを表示するテキストエリアを持ちます。このページはバッチジョブとサブミットして次のページへ遷移するためのボタンを提供します。

<body>
    <h1>The Phone Billing Example Application</h1>
    <h2>Log file</h2>
    <p>The batch job analyzes the following log file:</p>
    <textarea cols="90" rows="25" 
              readonly="true">#{jsfBean.createAndShowLog()}</textarea>
    <p> </p>
    <h:form>
        <h:commandButton value="Start Batch Job" 
                         action="#{jsfBean.startBatchJob()}" />
    </h:form>
</body>

このページは、ログファイルの表示とバッチジョブをサブミットするためにマネージドBeanのメソッドを呼び出します。

jobstarted.xhtmlはバッチジョブの現在のステータスをチェックするためのボタンとジョブが終了したときには請求を表示します。

<p>Current Status of the Job: <b>#{jsfBean.jobStatus}</b></p>
<h:dataTable var="_row" value="#{jsfBean.rowList}" 
             border="1" rendered="#{jsfBean.completed}">
    <!-- ... show results from jsfBean.rowList ... -->
</h:dataTable>
<!-- Render the check status button if the job has not finished -->
<h:form>
    <h:commandButton value="Check Status" 
                     rendered="#{jsfBean.completed==false}"
                     action="jobstarted" />
</h:form>

55.9.1.6 The Managed Bean

JsfBeanマネージドBeanはバッチランタイムへジョブをサブミットし、ジョブのステータスをチェックし、請求ごとのテキストファイルを読み込みます。

startBatchJobメソッドはバッチランタイムへジョブをサブミットします。

/* Submit the batch job to the batch runtime.
 * JSF Navigation method (return the name of the next page) */
public String startBatchJob() {
    jobOperator = BatchRuntime.getJobOperator();
    execID = jobOperator.start("phonebilling", null);
    return "jobstarted";
}

getJobStatusメソッドはジョブのステータスをチェックします。

/* Get the status of the job from the batch runtime */
public String getJobStatus() {
    return jobOperator.getJobExecution(execID).getBatchStatus().toString();
}

getRowListメソッドjobstarted.xhtmlで表示するために請求のリストを生成します。

55.9.2 Running the phonebilling Example Application

NetBeans IDEMavenでビルド・パッケージ・デプロイしてphonebillingサンプルアプリケーションを起動することが出来ます。

55.9.2.1 To Run the phonebilling Example Application Using NetBeans IDE

  1. GlassFishサーバが起動済みなことを確認してください。詳細な情報はChapter 2, "Using the Tutorial Examples"を確認してください。
  2. Fileメニューから、Open Projectを選択する。
  3. Open Projectダイアログボックスでサンプルアプリケーションのディレクトリに移動する。
    tut-install/examples/batch
  4. phonebillingディレクトリを選択する。
  5. Open Projectをクリックする。
  6. Projectsタブで、phonebillingプロジェクトを右クリックしてRunを選択する。
    このコマンドは、ビルドしてphonebilling.warを作成し、target/ディレクトリに配置し、サーバへデプロイし、webブラウザを立ち上げて、以下のURLを表示します。
    http://localhost:8080/phonebilling/

55.8.2.2 To Run the webserverlog Example Application Using Maven

  1. GlassFishサーバが起動済みなことを確認してください。詳細な情報はChapter 2, "Using the Tutorial Examples"を確認してください。
  2. ターミナルで下記ディレクトリに移動する。
    tut-install/examples/batch/phonebilling/
  3. アプリケーションをデプロイするために下記コマンドを入力する。
    mvn install
  4. webブラウザを起動して下記のアドレスを入力する。
    http://localhost:8080/phonebilling/

55.10 Further Information about Batch Processing

Java EEバッチ処理に関するより詳しい情報は、Batch Applications for the Java Platform specificationを参照してください。

http://www.jcp.org/en/jsr/detail?id=352

参考文献

関連エントリ

*1:原文では、下記の一覧からそれぞれの節のページへとリンクが張られているが、面倒なので省略している

*2:割と意味不明だけどcreates a bill if one does not exist for an accountがうまく訳せない……

*3:decision elementsのみ条件分岐と訳し、その他の要素のチャンク(chunk)とかステップ(step)とかはそのままカタカナにしている。固有名詞はそのまんまじゃないと逆に意図が伝わりにくいと思うんで。ただし『デシジョンエレメント』はさすがに座りが悪かったんで、コレだけは適当な日本語を当てることにした

*4:ここに限らずitemはそのまんまアイテムにしている。若干不自然だけど、まぁ意味は通じるし……

*5:a single call to the item writerって「ライターが一回だけ呼び出される」で良いのだろうか?

*6:原文はbatchとなっているが、batckletのミスタイプと思われる

*7:do not require separate packagingってこういう意味でいいのか?

*8:おそらく https://java.net/projects/javaeetutorial/sources/svn/show/trunk/examples/batch と思われる。