Asakusa Framework0.7.4(Direct I/O)でExcelファイルを入出力するライブラリーのメモ。
|
|
Asakusa FrameworkでExcelファイルを読み書きするDirect I/OのFormatクラスを作ってみた。
Apache POIを利用している。
Excelファイルを出力する際には、空のWorkbookから作ることも出来るし、テンプレート(雛形)のExcelファイルを読み込んで使うことも出来る。
Direct I/Oなので、出力ファイル名(ファイルの分割)やデータの出力順序はExporterの記述で制御可能。
このライブラリーでExcelファイルを読み込むことも出来るが、基本的にはExcelファイルの出力に使用する想定。
AsakusaFWではフローのテスト用に入出力をペアで作っておかないといけないので、読み込み機能も作ってある、という程度。
(入力として使いたい場合、テストデータを作成する為に、ファイルを出力できる必要がある。
出力として使いたい場合、テスト結果を検証する為に、ファイルを読み込める必要がある)
Excelファイルを読み込む際に分散はしないので、Excelファイルを読みたい場合は別のツール(例えばEmbulkのExcelパーサープラグインとか)でExcelファイルをcsvファイル等に変換してからAsakusaFWで読み込む方が良いような気がしている。
Asakusaプロジェクトのbuild.gradleに以下のライブラリーを追加すると、Excelファイル読み書き用のクラスが使えるようになる。
repositories { maven { url 'http://hishidama.github.io/mvnrepository' } } 〜 dependencies { compile group: 'com.asakusafw.sdk', name: 'asakusa-sdk-core', version: asakusafw.asakusafwVersion compile group: 'com.asakusafw.sdk', name: 'asakusa-sdk-directio', version: asakusafw.asakusafwVersion compile group: 'com.asakusafw.sdk', name: 'asakusa-sdk-windgate', version: asakusafw.asakusafwVersion compile group: 'jp.hishidama.asakusafw', name: 'asakusafw-spi-runtime', version: '0.+' testRuntime group: 'com.asakusafw.sdk', name: 'asakusa-sdk-test-emulation', version: asakusafw.asakusafwVersion 〜 }
実行環境(実際のHadoopクラスター上)で動かす為には、asakusafw-spi-runtime自身とそれが依存しているライブラリー(のjarファイル)を実行環境に配置する必要がある。[2016-02-11]
AsakusaFW以外のライブラリーのjarファイルは実行環境の「$ASAKUSA_HOME/ext/lib」に配置する。
手動でjarファイルをダウンロードしてきてext/libに置いてもいいが、build.gradleに記述することで配置する方法もある。
実行環境を構築する為のデプロイメントアーカイブに含める方法についてはドキュメントの『外部ライブラリの配置』に書かれている。
AsakusaFW 0.7.1以降では、build.gradleに以下のように記述する。
asakusafwOrganizer { extension { libraries += ["com.asakusafw.sandbox:asakusa-directio-runtime-ext:${asakusafw.asakusafwVersion}"] libraries += ["jp.hishidama.asakusafw:asakusafw-spi-runtime:0.+"] libraries += ["org.apache.poi:poi:3.13"] libraries += ["org.apache.poi:poi-ooxml:3.13"] libraries += ["org.apache.poi:poi-ooxml-schemas:3.13"] libraries += ["org.apache.xmlbeans:xmlbeans:2.6.0"] } 〜 }
依存ライブラリーを推移的に取得してはくれない(機械的にそうするとHadoopのライブラリーまで持ってきてしまう)ので、必要なライブラリーを全て書く必要がある。
拙作asakusafw-spi-runtimeの場合、Apache POIのライブラリーが必要となる。
(Maven Repositoryのpoi-ooxmlのページを見れば依存関係(Dependencies)が分かるので、それを辿っていく。なお、stax-apiは不要(asakusafw-spi-runtimeのbuild.gradleでも、stax-apiはわざわざexcludeしてある))
category_summaryをExcelで出力する例。
src/main/resourcesの下にあるcategory_summary.xlsというファイルをテンプレートとして読み込み、そのシート上にデータを出力して、Direct I/Oとしてファイルを出力する。
Excelファイル(ワークブック・シート)を操作するクラス。
Apache POIを使っているので、このFormatクラスをカスタマイズすれば何でも自由に出来る。
import java.util.Arrays; import java.util.List; import jp.hishidama.asakusafw_spi.excel.AbstractExcelFormat; import jp.hishidama.asakusafw_spi.excel.ExcelReader; import jp.hishidama.asakusafw_spi.excel.ExcelWriter; import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.CellStyle; import org.apache.poi.ss.usermodel.Row; import org.apache.poi.ss.usermodel.Sheet; import org.apache.poi.ss.usermodel.Workbook; import com.example.modelgen.dmdl.model.CategorySummary;
public class CategorySummaryExcelFormat extends AbstractExcelFormat<CategorySummary> {
@Override public Class<CategorySummary> getSupportedType() { return CategorySummary.class; } @Override protected ExcelReader<CategorySummary> createReader() { return new Reader(); } @Override protected ExcelWriter<CategorySummary> createWriter() { return new Writer(); }
protected static class Reader extends ExcelReader<CategorySummary> { @Override protected List<String> getSheetNames(Workbook workbook) { // 読み込むシート名 return Arrays.asList("集計結果"); } @Override protected int getSkipRows(Sheet sheet) { // 先頭1行(ヘッダー行)を読み飛ばす。 return 1; } @Override protected boolean fillTo(Row row, CategorySummary object) { // Excelの1行分の内容をAsakusaFWのデータモデルへ移送する。 fill(row, 0, object.getCategoryCodeOption()); fill(row, 1, object.getAmountTotalOption()); fill(row, 2, object.getSellingPriceTotalOption()); return true; } }
protected static class Writer extends ExcelWriter<CategorySummary> { @Override protected String getTemplateExcelFile() { // テンプレートとして使用するExcelファイル名 // src/main/resources内の、当javaソースと同じパッケージの場所にExcelファイルを置いておく必要がある。 return "category_summary.xls"; } @Override protected void initializeWorkbook(Workbook workbook) { // 当メソッドはWorkbookインスタンスが生成された直後に呼ばれる。 // 今回の例では、「template」というシートを「集計結果」というシート名に変更している。 int index = workbook.getSheetIndex("template"); workbook.setSheetName(index, "集計結果"); } @Override protected boolean processHeader(Sheet sheet, int rowIndex) { // 先頭行(rowIndex==0)をヘッダー行として無視する。(データ出力をしない) return rowIndex == 0; } @Override protected void emit(Row row, CategorySummary object) { // AsakusaFWのデータモデルからExcelの1行分を移送する。 emit(row, 0, object.getCategoryCodeOption()); emit(row, 1, object.getAmountTotalOption()); emit(row, 2, object.getSellingPriceTotalOption()); } @Override protected void decorate(Row row, int columnIndex, Cell cell) { // 各emitメソッドから呼ばれる。当メソッドでセルのスタイルを設定する。 if (cell == null) { return; } // 今回の例では、シートの2行目(データ行としての先頭行)のスタイルをそのまま生かす。 if (row.getRowNum() == 1) { return; } // データ行としての2行目以降は、シートの2行目のスタイルをそのままコピーする。 Sheet sheet = row.getSheet(); Cell first = sheet.getRow(1).getCell(columnIndex); CellStyle style = first.getCellStyle(); cell.setCellStyle(style); } } }
Exporterの定義。
getFormat()でExcel用のFormatクラスを指定する以外は、普通のDirect I/OのExporterと同じ。
(DMDLで@directioを指定しておく必要は無い。そこで生成される抽象クラスを使うわけではないので)
import java.util.Arrays; import java.util.List; import com.asakusafw.runtime.directio.DataFormat; import com.asakusafw.vocabulary.directio.DirectFileOutputDescription; import com.example.jobflow.format.CategorySummaryExcelFormat; import com.example.modelgen.dmdl.model.CategorySummary;
public class CategorySummaryToExcel extends DirectFileOutputDescription {
@Override
public Class<?> getModelType() {
return CategorySummary.class;
}
@Override
public Class<? extends DataFormat<?>> getFormat() {
// Excel用のDataFormat
return CategorySummaryExcelFormat.class;
}
@Override
public String getBasePath() {
return "result/category";
}
@Override
public String getResourcePattern() {
return "result.xls";
}
@Override
public List<String> getOrder() {
return Arrays.asList("-selling_price_total");
}
}
テンプレートとして使用するExcelファイル。
WriterのgetTemplateExcelFileメソッドでテンプレートファイルを指定すると、Workbookを作る際にテンプレートファイルが読み込まれる。
このシートを利用してExcelファイルを作成することになる。
(テンプレートファイルを置く位置は、テンプレートファイルを読み込むWriterと同じパッケージ(具象クラスが別のクラスの内部にある場合は、外側のクラスのパッケージ)である必要がある)
A | B | C | D | |
1 | カテゴリーコード | 販売数量 | 売上合計 | |
2 | ||||
3 | ||||
4 |
今回の例では、ヘッダー行のスタイルはそのまま生かしつつ(WriterのprocessHeaderメソッド)、
データ行は2行目のスタイルをコピーしている(Writerのdecorateメソッド)。
(テンプレートファイルを指定しなかった場合は、空のWorkbookが作られる。この場合でもプログラム内でCellStyleを作ってやれば、セルを修飾することは可能)
ところで、AsakusaFW 0.7.6(ビルド方法にGradleを使うようになった頃から?)では、src/main/resourcesにリソースファイルを置いても、バッチをコンパイルして作られるJobのjarファイルにリソースファイルが含まれないというバグがある(苦笑)[2016-02-11]
Asakusa on Sparkのバッチでは、回避策としてbuild.gradleに以下のように書いておけば、Jobのjarファイルにリソースファイルが含まれるようになる。
dependencies { 〜 sparkCompileBatchapps.embed << file('src/main/resources') }