S-JIS[2015-10-18/2016-02-11] 変更履歴

Asakusa Framework Excelファイル入出力

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ファイルを読みたい場合は別のツール(例えばEmbulkExcelパーサープラグインとか)でExcelファイルをcsvファイル等に変換してからAsakusaFWで読み込む方が良いような気がしている。


インストール方法

Asakusaプロジェクトのbuild.gradleに以下のライブラリーを追加すると、Excelファイル読み書き用のクラスが使えるようになる。

build.gradle:

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に以下のように記述する。

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としてファイルを出力する。


Formatクラス

Excelファイル(ワークブック・シート)を操作するクラス。

Apache POIを使っているので、このFormatクラスをカスタマイズすれば何でも自由に出来る。

CategorySummaryExcelFormat.java:

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クラス

Exporterの定義。
getFormat()でExcel用のFormatクラスを指定する以外は、普通のDirect I/OのExporterと同じ。
(DMDLで@directioを指定しておく必要は無い。そこで生成される抽象クラスを使うわけではないので)

CategorySummaryToExcel.java:

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ファイル

テンプレートとして使用するExcelファイル。
WriterのgetTemplateExcelFileメソッドでテンプレートファイルを指定すると、Workbookを作る際にテンプレートファイルが読み込まれる。
このシートを利用してExcelファイルを作成することになる。
(テンプレートファイルを置く位置は、テンプレートファイルを読み込むWriterと同じパッケージ(具象クラスが別のクラスの内部にある場合は、外側のクラスのパッケージ)である必要がある)

src/main/resources/〜/category_summary.xls

  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ファイルにリソースファイルが含まれるようになる。

build.gradle:

dependencies {
〜
    sparkCompileBatchapps.embed << file('src/main/resources')
}

Direct I/Oへ戻る / AsakusaFW目次へ戻る / 技術メモへ戻る
メールの送信先:ひしだま