jBatch のお試し実装

jBatch(Batch Application for the java Platform) のお試し実装メモ。

#環境
##OS
Windows7 64bit

##Java EE 環境
GlassFish OpenSource Edition 4.0

#実装
##Chunk の実装
Chunk を実装する場合は、 `ItemReader` 、 `ItemProcessor` 、 `ItemWriter` の３つのインターフェースを実装したクラスを用意する。

###ItemReader の実装
```java:MyItemReader
package jbatch.chunk;

import java.io.Serializable;

import javax.batch.api.chunk.ItemReader;

public class MyItemReader implements ItemReader {
    
    @Override
    public void open(Serializable checkPoint) throws Exception {
        System.out.println("[Reader] open");
    }
    
    private int counter;
    
    @Override
    public Object readItem() throws Exception {
        String item;
        
        if (counter < 22) {
            item = String.format("item-%03d", ++counter);
        } else {
            item = null;
        }
        
        System.out.println("[Reader] readItem. item = " + item);
        
        return item;
    }
    
    @Override
    public void close() throws Exception {
        System.out.println("[Reader] close");
    }
    
    @Override
    public Serializable checkpointInfo() throws Exception {
        System.out.println("[Reader] checkpointInfo");
        return null;
    }
}
```

###ItemProcessor の実装
```java:MyItemProcessor
package jbatch.chunk;

import javax.batch.api.chunk.ItemProcessor;

public class MyItemProcessor implements ItemProcessor {
    
    @Override
    public Object processItem(Object item) throws Exception {
        System.out.println("[Processor] item = " + item);
        return ((String)item).toUpperCase();
    }
}
```

###ItemWriter の実装
```java:MyItemWriter
package jbatch.chunk;

import java.io.Serializable;
import java.util.List;

import javax.batch.api.chunk.ItemWriter;

public class MyItemWriter implements ItemWriter {
    
    @Override
    public void open(Serializable checkPoint) throws Exception {
        System.out.println("[Writer] open");
    }
    
    @Override
    public void writeItems(List<Object> items) throws Exception {
        System.out.println("[Writer] writeItems. items = " + items);
    }
    
    @Override
    public void close() throws Exception {
        System.out.println("[Writer] close");
    }
    
    @Override
    public Serializable checkpointInfo() throws Exception {
        System.out.println("[Writer] checkpointInfo");
        return null;
    }
}
```

##Batchlet の実装
Batchlet を実装する場合は、 `Batchlet` インターフェースを実装したクラスを用意する。

```java:MyBatchlet
package jbatch.batchlet;

import javax.batch.api.Batchlet;

public class MyBatchlet implements Batchlet {
    
    @Override
    public String process() throws Exception {
        System.out.println("[Batchlet] process");
        return null;
    }
    
    @Override
    public void stop() throws Exception {
        System.out.println("[Batchlet] stop");
    }
}
```

##ジョブXMLの作成
ジョブの内容を定義した XML ファイルを作成して、 `WEB-INF/classes/META-INF/batch-jobs` の下に配置する。

```xml:sample-job.xml
<?xml version="1.0" encoding="UTF-8"?>
<job id="sample-job"
     xmlns="http://xmlns.jcp.org/xml/ns/javaee" 
     version="1.0">
  
  <step id="sample-chunk" next="sample-batchlet">
    <chunk>
      <reader    ref="jbatch.chunk.MyItemReader"    />
      <processor ref="jbatch.chunk.MyItemProcessor" />
      <writer    ref="jbatch.chunk.MyItemWriter"    />
    </chunk>
  </step>
  
  <step id="sample-batchlet">
    <batchlet ref="jbatch.batchlet.MyBatchlet" />
  </step>
  
</job>
```

##エントリポイント用の JAX-RS サービスを実装
jBatch のジョブを実行するためのエントリポイントとして、 JAX-RS のサービスを実装する。

```java:MyApplication
package jbatch.rest;

import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;

@ApplicationPath("rest")
public class MyApplication extends Application {
}
```

```java:JBatchResource 
package jbatch.rest;

import javax.batch.operations.JobOperator;
import javax.batch.runtime.BatchRuntime;
import javax.ws.rs.GET;
import javax.ws.rs.Path;

@Path("sample")
public class JBatchResource {
    
    @GET
    public String start() {
        JobOperator job = BatchRuntime.getJobOperator();
        
        System.out.println("[REST] バッチ呼び出し前");
        long id = job.start("sample-job", null);
        System.out.println("[REST] バッチ呼び出し後");
        
        return "jBatch Start. id = " + id;
    }
}
```

##Eclipse プロジェクトの様子
![Eclipseプロジェクトの様子](https://qiita-image-store.s3.amazonaws.com/0/28302/c6fe0c1a-0b05-30d7-e572-7689dfebb364.jpeg)

#実行結果
Web ブラウザで `http://localhost:8080/jbatch/rest/sample` にアクセスすると、以下のようにコンソールに出力される。

※ホスト名（localhost）、ポート（8080）、コンテキストパス（jbatch）は適宜読み替え。

```:実行結果
[REST] バッチ呼び出し前
[REST] バッチ呼び出し後
[Reader] open
[Writer] open
[Reader] readItem. item = item-001
[Processor] item = item-001
[Reader] readItem. item = item-002
[Processor] item = item-002
[Reader] readItem. item = item-003
[Processor] item = item-003
[Reader] readItem. item = item-004
[Processor] item = item-004
[Reader] readItem. item = item-005
[Processor] item = item-005
[Reader] readItem. item = item-006
[Processor] item = item-006
[Reader] readItem. item = item-007
[Processor] item = item-007
[Reader] readItem. item = item-008
[Processor] item = item-008
[Reader] readItem. item = item-009
[Processor] item = item-009
[Reader] readItem. item = item-010
[Processor] item = item-010
[Writer] writeItems. items = [ITEM-001, ITEM-002, ITEM-003, ITEM-004, ITEM-005, ITEM-006, ITEM-007, ITEM-008, ITEM-009, ITEM-010]
[Reader] checkpointInfo
[Writer] checkpointInfo
[Reader] readItem. item = item-011
[Processor] item = item-011
[Reader] readItem. item = item-012
[Processor] item = item-012
[Reader] readItem. item = item-013
[Processor] item = item-013
[Reader] readItem. item = item-014
[Processor] item = item-014
[Reader] readItem. item = item-015
[Processor] item = item-015
[Reader] readItem. item = item-016
[Processor] item = item-016
[Reader] readItem. item = item-017
[Processor] item = item-017
[Reader] readItem. item = item-018
[Processor] item = item-018
[Reader] readItem. item = item-019
[Processor] item = item-019
[Reader] readItem. item = item-020
[Processor] item = item-020
[Writer] writeItems. items = [ITEM-011, ITEM-012, ITEM-013, ITEM-014, ITEM-015, ITEM-016, ITEM-017, ITEM-018, ITEM-019, ITEM-020]
[Reader] checkpointInfo
[Writer] checkpointInfo
[Reader] readItem. item = item-021
[Processor] item = item-021
[Reader] readItem. item = item-022
[Processor] item = item-022
[Reader] readItem. item = null
[Writer] writeItems. items = [ITEM-021, ITEM-022]
[Reader] checkpointInfo
[Writer] checkpointInfo
[Reader] close
[Writer] close
[Batchlet] process
```

`JobOperator#start(String, Properties)` の呼び出し後は非同期でジョブが実行されている。

#ジョブXML について
##ファイルは WEB-INF/classes/META-INF/batch-jobs の下に配置する
前述したとおり、ジョブXML は `WEB-INF/classes/META-INF/batch-jobs` の下に配置する。

##job タグの id 属性

```xml:sample-job.xml
<?xml version="1.0" encoding="UTF-8"?>
<job id="sample-job"
     ...>
</job>
```

- <del>ジョブXML のファイル名は、`job` タグの `id` 属性と一致させなければならない。</del>

(2016/03/06 追記)
上記間違ってました。すみません。。。
ジョブ XML のファイル名と `<job>` タグの `id` 属性は、一致させる必要はないみたいです。

`<job>` タグの `id` 属性は任意の値で良いようです。
JSR-352 によると、

> Specifies the logical name of the job and is used for identification purposes. It must be a valid XML string value. This is a required attribute. 
> 
> （意訳）
> ジョブの論理名を指定し、識別のために使用される。
> この値は、 XML の文字列値として有効な値でなければならない。
> これは必須の属性です。

ということで、論理名なのでファイル名と一致させる必要はないようです。

##step タグについて

```xml:
<step id="sample-chunk" next="sample-batchlet">
  ...
</step>

<step id="sample-batchlet">
  ...
</step>
```

+ ジョブXML では、 `step` タグを使ってジョブの具体的な実行内容（順序）を記述する。
+ `step` タグは `id` 属性が __必須__ 。
+ `next` 属性に他の `step` タグの `id` 属性を指定することで `step` の実行順序を制御できる。

##ref 属性には CDI の Named で設定した名前も指定できる

前述の例では、 `ref` 属性には FQCN を指定していたが、下記のように `Named` アノテーションで設定した名前も指定できる。

```java:Namedで名前付け
import javax.enterprise.context.Dependent;
import javax.inject.Named;

@Dependent
@Named("myItemWriter")
public class MyItemWriter implements ItemWriter {
    ...
}
```

```xml:Namedで設定した名前を指定
<step id="sample-chunk" next="sample-batchlet">
  <chunk>
    ...
    <processor ref="myItemProcessor" />
    ...
  </chunk>
</step>
```

なお、 `javax.enterprise.context.Dependent` アノテーションは `beans.xml` がなくても CDI が有効になるようにするために付与している。（詳しくは[Java EE環境におけるCDIのデフォルト化 - 別に危なくないプログラミング日記](http://n-agetsuma.hatenablog.com/entry/2013/08/18/130358)を参照）。

#ジョブの起動方法
```java:
JobOperator job = BatchRuntime.getJobOperator();
job.start("sample-job", null);
```

`BatchRuntime#getJobOperator()` メソッドで `JobOperator` のインスタンスを取得する。
次に、 `start(String, Properties)` メソッドでジョブを実行する。<del>このとき、第一引数で実行するジョブ ID を指定する。ジョブ ID とは、ジョブXML の `job` タグで指定した `id` 属性の値のこと。</del>

```xml:ジョブID
<?xml version="1.0" encoding="UTF-8"?>
<job id="sample-job"
     ...>
  ...
</job>
```

(2016/03/06 追記)
第一引数に指定するのは、拡張子を除いたジョブXMLのファイル名でした。すみません。。。

以下、 Javadoc の `JobOperator#start(String, Properties)` を訳しました。

> Note the Job XML describing the job is first searched for by name according to a means prescribed by the batch runtime implementation. This may vary by implementation. If the Job XML is not found by that means, then the batch runtime must search for the specified Job XML as a resource from the META-INF/batch-jobs directory based on the current class loader. Job XML files under META-INF/batch-jobs directory follow a naming convention of "name".xml where "name" is the value of the jobXMLName parameter (see below).
> 
> （意訳）
> 注意点として、 名前を使った Job XML の最初の検索は、バッチ実行環境の実装が指定します。これは、実装によって異なるかもしれません。
> もし最初の検索で Job XML が見つからない場合、バッチ実行環境は現在のクラスローダを使って `META-INF/batch-jobs` ディレクトリからリソースとして Job XML を検索しなければなりません。
> `META-INF/batch-jobs` ディレクトリ以下の Job XML ファイルは、 `"name".xml` という名前で定義します（`"name"` は、第一引数の `jobXMLName` で指定する値です）。

- [JobOperator (Java(TM) EE 7 Specification APIs)](http://docs.oracle.com/javaee/7/api/javax/batch/operations/JobOperator.html#start-java.lang.String-java.util.Properties-)

#参考
+ [JSR 352 “Batch Applications for the Java Platform” - slideshare](http://www.coppermine.jp/mt/mt-search.cgi?IncludeBlogs=45,46&tag=JBatch&limit=20)
+ [The Java EE 7 Tutorial:Batch Processing | Java EE Documentation](http://docs.oracle.com/javaee/7/tutorial/doc/batch-processing.htm#GKJIQ6)
+ [Batch Applications for the Java Platform (JSR-352) Java EE 7 New Feature - slideshare](http://www.slideshare.net/OracleMiddleJP/batch-applications-for-the-java-platform-jsr352-java-ee-7-new-feature)