Apache POI を用いて Excel ファイルを読み込む方法を説明します。また、得られた情報を元にモデルとモデル項目を作成し、リポジトリへ書き込む方法を説明します。

Apache POI は Excel ファイルの内容(シート、セル)を読み書きすることのできるライブラリです。オープンソースで提供されており、Wagby に同梱されています。このライブラリを使って、アップロードされた Excel ファイルを読み込むプログラムを開発する方法を説明します。

package jp.jasminesoft.jfc.tools.repository.converter.excel;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.ss.usermodel.DateUtil;
import org.apache.poi.ss.usermodel.WorkbookFactory;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;

import jp.jasminesoft.jfc.tools.repository.converter.ConvertProcessorBase;

public class ExcelConverterProcessor extends ConvertProcessorBase {
    private Workbook workbook;

    @Override
    public void init(Map<String, String> environment, List<String> messageList) throws IOException {
        super.init(environment, messageList);
        
        String filename = environment.get("filename");
        if (StringUtils.isBlank(filename)) {
            messageList.add("[ERROR] No file is specified.");
            return;
        }
        try {
            workbook = WorkbookFactory.create(new FileInputStream(filename));
        } catch (Exception e) {
            workbook = null;
            throw new IOException(e.getMessage());
        }
        if (debug) {
            System.out.println("environment="+environment);
        }
    }

    @Override
    public void process() {
        if (workbook == null) return;

        int sheetSize = workbook.getNumberOfSheets();
        for (int sheetIndex = 0; sheetIndex < sheetSize; sheetIndex++) {
            Sheet sheet = workbook.getSheetAt(sheetIndex);
            String sheetName = sheet.getSheetName();
            if (debug) {
                System.out.println("[Debug] sheetName="+sheetName);
            }
        }
    }
}
  • WagbyDesignerが用意する「アップロード」欄に、一つのファイルをアップロードすることができます。アップロードされたファイルは wagbydesinger/temp フォルダにテンポラリファイル名(一時的に割り当てられたファイル名)で保存されます。そのファイル名を environment マップのキー filename から取得することができます。すなわち開発者はファイルのアップロード処理を作成する必要はありません。
  • 開発者は得られたファイル名を使って、ファイルをオープンすることができます。上の例では Apache POI が提供する WorkbookFactory クラスを使って Excel ファイルをオープンしています。workbook の詳細は Apache POI のマニュアルをお読みください。
  • 上のクラスでは workbook をインスタンス変数として管理しており、process メソッドからも利用できるようにしています。サンプルとして workbook に含まれる sheet の名前をコンソールに出力するようにしています。

コンパイル

参照する jar ファイルに commons-lang3.jar と poi.jar, poi-ooxml.jar を追加します。いずれも WagbyDesigner の WEB-INF/lib フォルダに含まれています。

Windows OS の場合:

javac -cp .;classes;lib\j_util.jar;lib\commons-lang3.jar;lib\poi.jar;lib\poi-ooxml.jar -d classes src\com\example\SampleConverterProcessor.java

Linux, Mac OS X の場合:

javac -cp .:classes:lib/j_util.jar:lib/commons-lang3.jar:lib/poi.jar:lib/poi-ooxml.jar -d classes src/com/example/SampleConverterProcessor.java

実行例

sample.xlsx というファイルを (WagbyDesignerに) アップロードした例を紹介します。

図1 sample.xlsx をアップロードする

コンソールには次のようにシート名が表示されます。

...
environment={filename=/Users/joe/Desktop/test/Wagby-8.1.2/wagbydesigner/temp/RepositoryConervter1914110958805521156.xlsx, debug=true, ...}
[Debug] sheetName=顧客

読み込んだExcelファイルのシート名に対応した、空のモデルをリポジトリに生成してみます。 Wagby が提供する ModelInfo と RepositoryManager を利用します。ModelInfo は一つのモデルに関する設計情報を保持します。RepositoryManager は、リポジトリファイル(物理的なファイル、repository/trunk 以下に保存されるテキストファイル)を操作するクラスです。

package com.example;
...
import jp.jasminesoft.jfc.tools.repository.RepositoryManager;
import jp.jasminesoft.jfc.tools.xls2appschema.ModelInfo;
...

public class SampleConverterProcessor extends ConvertProcessorBase {
    ...

    @Override
    public void process() {
        ...
        int menuorderBase = 100000; // 起点
        int countOfMenu = 0;

        try {
            int sheetSize = workbook.getNumberOfSheets();
            for (int sheetIndex = 0; sheetIndex < sheetSize; sheetIndex++) {
                Sheet sheet = workbook.getSheetAt(sheetIndex);
                String sheetName = sheet.getSheetName();
                if (debug) {
                    System.out.println("[Debug] sheetName="+sheetName);
                }
                String modelId = "model" + (sheetIndex+1);
                ModelInfo minfo = createModelInfo(modelId, sheetName, sheetName);

                Map<String, String> modelInfoMap = minfo.getModelInfoMap();
                //検索画面への遷移をメニューに配置する
                modelInfoMap.put("action/@menurefShowlist", "true");
                // 検索画面と一覧表示を同一画面で扱う
                modelInfoMap.put("model/@presentationShowListOnescreen", "true");

                int myMenuOrder = menuorderBase + countOfMenu * 100;
                // 検索機能をメニューに用意する
                modelInfoMap.put("action/@menuorderSelect", String.valueOf(myMenuOrder)); 
                modelInfoMap.put("model/@menuorder", String.valueOf(menuorderBase));
                 
                repman.getModelMap().put(modelId, minfo);
                repman.saveModelMap(modelId);
                countOfMenu++;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
  • このプログラムは、シートの数に対応した新しいモデルを作成します。
  • モデルID は "model" + 連番 としています。モデル名はシート名としています。
  • createModelInfo メソッドは、継承元のクラスが提供しています。第一引数はモデルIDを、第二引数はモデル名を指定します。第三引数は「モデル > その他 > モデル情報 > 説明」に反映される情報になります。ここではモデル名と同じとしています。
  • createModelInfo メソッドを呼び出すことで、新しいモデル(の設計情報の入れものとなる)ModelInfo を作成します。上の例では、これを minfo と表記しています。
  • minfo の getModelInfoMap メソッドは key-value 形式のマップを返します。これがモデルに関する設計情報を格納するものです。このマップにさまざまな設計情報(リポジトリ)を設定することができます。上の例では action/@menurefShowlistmodel/@presentationShowListOnescreen を設定しています。
  • さらにメニューに検索ボタンを用意しています。メニューは表示順という値をもっており、これも設定しています。メニュー表示順は今回、100000,100100,100200,... と 100 ずつ増えた値を機械的に設定するようにしています。
  • 38行目と39行目の処理で、リポジトリを書き込んでいます。RepositoryManager オブジェクト repman の getModelMap() メソッドは現在のモデル全体を表現するマップを返します。このマップに modelId をキーに、リポジトリ (minfo) をセットします。この段階ではまだメモリ上に記録されています。40行目の saveModelMap メソッドで、物理ファイルへの書き込みを行います。

コンパイル

参照するライブラリに j_xls2appschema.jar を追加します。(先頭が "j_" で始まる jar ファイルは、Wagby が提供するライブラリです。)

Windows OS の場合:

javac -cp .;classes;lib\j_xls2appschema.jar;lib\j_util.jar;lib\commons-lang3.jar;lib\poi.jar;lib\poi-ooxml.jar -d classes src\com\example\SampleConverterProcessor.java

Linux, Mac OS の場合:

javac -cp .:classes:lib/j_xls2appschema.jar:lib/j_util.jar:lib/commons-lang3.jar:lib/poi.jar:lib/poi-ooxml.jar -d classes src/com/example/SampleConverterProcessor.java

コンパイルが成功すると WEB-INF/classes フォルダ内に com/example/SampleConverterProcessor.class が生成されます。

実行

インポートを実行すると、シートの数だけモデルが作成されます。この段階では、各モデルの中身(モデル項目)は空です。主キー項目もないためビルドするとエラーになります。ビルドはもう少しお待ちください。

モデル項目は ModelitemInfo クラスが管理します。この ModelitemInfo を使って、主キー項目を付与するように変更してみます。

package com.example;

...
import jp.jasminesoft.jfc.tools.xls2appschema.ModelInfo;
import jp.jasminesoft.jfc.tools.xls2appschema.ModelitemInfo;
...

public class SampleConverterProcessor extends ConvertProcessorBase {
    ...
    @Override
    public void process() {
        ...
        try {
            int sheetSize = workbook.getNumberOfSheets();
            for (int sheetIndex = 0; sheetIndex < sheetSize; sheetIndex++) {
                Sheet sheet = workbook.getSheetAt(sheetIndex);
                ...
                int lineNumber = 1;

                // モデル項目
                String item = "pkey";
                String label = "主キー";
                Map<ModelitemInfo, String> modelitemInfoMap = minfo.getModelitemInfoMap();    

                modelitemInfoMap.put(new ModelitemInfo("model/modelitem/@label", item), label);
                modelitemInfoMap.put(new ModelitemInfo("model/modelitem/@labelWithoutLayout", item), label);
                modelitemInfoMap.put(new ModelitemInfo("model/modelitem/@name", item), item);
                modelitemInfoMap.put(new ModelitemInfo("model/modelitem/@filter", item), "intFilter");
                modelitemInfoMap.put(new ModelitemInfo("model/modelitem/@type", item),  "number");
                modelitemInfoMap.put(new ModelitemInfo("presentation/displayitem/@type", item), "numberformat");
                modelitemInfoMap.put(new ModelitemInfo("model/modelitem/@primaryKey", item), "true");
                modelitemInfoMap.put(new ModelitemInfo("model/modelitem/notnull", item), "true");
                modelitemInfoMap.put(new ModelitemInfo("model/primaryKey/@autoid", item), "true");

                modelitemInfoMap.put(new ModelitemInfo("ref_model/@name", item), modelId);
                modelitemInfoMap.put(new ModelitemInfo("ref_model/modelitem/@name", item), item);
                modelitemInfoMap.put(new ModelitemInfo("ref_model/modelitem/@type", item), "number");
                modelitemInfoMap.put(new ModelitemInfo("ref_model/modelitem/@primaryKey", item), "true");

                modelitemInfoMap.put(new ModelitemInfo("model/modelitem/@__linenumber", item), 
                    Integer.toString(lineNumber++));
                 
                repman.getModelMap().put(modelId, minfo);
                repman.saveModelMap(modelId);
                countOfMenu++;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
  • minfo オブジェクトの getModelitemInfoMap メソッドを使うと、このモデルが管理するモデル項目のマップを取得することができます。
  • modelitemInfoMap のキーに ModelitemInfo オブジェクトをセットします。このオブジェクトは生成時にリポジトリキーと項目IDをコンストラクタの引数に渡して生成します。modelitemInfoMap の値は、リポジトリキーに対応する値を格納します。
  • [重要] 項目ごとにユニークかつ連続したmodel/modelitem/@__linenumberをセットしてください。この値が未設定の項目を含むモデルは破損されたものとみなされます。

まとめますと、モデルに関するリポジトリキー(と値)の格納には ModelInfo を、モデル項目に関するリポジトリキー(と値)の格納には ModelitemInfo クラスをそれぞれ用います。

コンパイルと実行

コンパイル手順は前節の説明と同じです。ビルド後、実行すると一つの項目(主キー)だけのモデルが用意されていることがわかります。

Wagbyの設計情報(リポジトリ)はモデル毎に管理されます。さらにモデルに共通するもの(画面機能、メニュー、権限、帳票設定)と、モデル項目毎に設定されるもの(モデル項目詳細ダイアログで設定されるもの)に大別されます。

リポジトリキーの一覧表はこちらにあります。 しかしこの表をすべて設定する必要はありません。未設定の場合にはデフォルト値が適用されるようになっています。

特定の機能に関するリポジトリキーを調べる (1) コンソールの利用

必要なリポジトリキーを調べるために、WagbyDesignerのコンソールを活用してください。WagbyDesignerで何らかの設定を行うと、コンソールにリポジトリキーと値が表示されます。

例えば "画面 > ダウンロード画面 > 画面を作成する" を有効にしたとき、次のようなメッセージが表示されます。

2018-11-xx 11:00:00 [DEBUG jp.jasminesoft.jfc.tools.repository.validator.QuickValidator validate] [QuickValidator] model1, model/@csvOutput=true

このメッセージから、リポジトリキーはmodel/@csvOutputで、値はtrueが設定されることがわかります。

特定の機能に関するリポジトリキーを調べる (2) 一括設定の利用

モデル項目の一括設定を使うと、項目に設定できるリポジトリキーを閲覧することができます。

特定の機能に関するリポジトリキーを調べる (3) デフォルト値の確認

次のファイルにデフォルト値が格納されています。

モデル

repository/trunk/jfcDesignerTemplateModel/jfcDesignerTemplateModel.txt

モデル項目

repository/trunk/jfcDesignerTemplateModelitem/jfcDesignerTemplateModelitem/templateItem/templateItem.txt

ここまで説明したソースコード全体を示します。

package com.example;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.ss.usermodel.DateUtil;
import org.apache.poi.ss.usermodel.WorkbookFactory;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;

import jp.jasminesoft.jfc.tools.repository.RepositoryManager;
import jp.jasminesoft.jfc.tools.xls2appschema.ModelInfo;
import jp.jasminesoft.jfc.tools.xls2appschema.ModelitemInfo;

import jp.jasminesoft.jfc.tools.repository.converter.ConvertProcessorBase;

public class SampleConverterProcessor extends ConvertProcessorBase {
    private Workbook workbook;

    @Override
    public void init(Map<String, String> environment, List<String> messageList) throws IOException {
        super.init(environment, messageList);
        
        String filename = environment.get("filename");
        if (StringUtils.isBlank(filename)) {
            messageList.add("[ERROR] No file is specified.");
            return;
        }
        try {
            workbook = WorkbookFactory.create(new FileInputStream(filename));
        } catch (Exception e) {
            workbook = null;
            throw new IOException(e.getMessage());
        }
        if (debug) {
            System.out.println("environment="+environment);
        }
    }

    @Override
    public void process() {
        if (workbook == null) return;

        int menuorderBase = 100000; // 起点
        int countOfMenu = 0;

        try {
            int sheetSize = workbook.getNumberOfSheets();
            for (int sheetIndex = 0; sheetIndex < sheetSize; sheetIndex++) {
                Sheet sheet = workbook.getSheetAt(sheetIndex);
                String sheetName = sheet.getSheetName();
                if (debug) {
                    System.out.println("[Debug] sheetName="+sheetName);
                }
                String modelId = "model" + (sheetIndex+1);
                ModelInfo minfo = createModelInfo(modelId, sheetName, sheetName);

                Map<String, String> modelInfoMap = minfo.getModelInfoMap();
                //検索画面への遷移をメニューに配置する
                modelInfoMap.put("action/@menurefShowlist", "true");
                // 検索画面と一覧表示を同一画面で扱う
                modelInfoMap.put("model/@presentationShowListOnescreen", "true");

                // メニューならび
                int myMenuOrder = menuorderBase + countOfMenu * 100;
                modelInfoMap.put("action/@menuorderSelect", String.valueOf(myMenuOrder)); 
                modelInfoMap.put("model/@menuorder", String.valueOf(menuorderBase));

                int lineNumber = 1;

                // モデル項目
                String item = "pkey";
                String label = "主キー";
                Map<ModelitemInfo, String> modelitemInfoMap = minfo.getModelitemInfoMap();    

                modelitemInfoMap.put(new ModelitemInfo("model/modelitem/@label", item), label);
                modelitemInfoMap.put(new ModelitemInfo("model/modelitem/@labelWithoutLayout", item), label);
                modelitemInfoMap.put(new ModelitemInfo("model/modelitem/@name", item), item);
                modelitemInfoMap.put(new ModelitemInfo("model/modelitem/@filter", item), "intFilter");
                modelitemInfoMap.put(new ModelitemInfo("model/modelitem/@type", item),  "number");
                modelitemInfoMap.put(new ModelitemInfo("presentation/displayitem/@type", item), "numberformat");
                modelitemInfoMap.put(new ModelitemInfo("model/modelitem/@primaryKey", item), "true");
                modelitemInfoMap.put(new ModelitemInfo("model/modelitem/notnull", item), "true");
                modelitemInfoMap.put(new ModelitemInfo("model/primaryKey/@autoid", item), "true");

                modelitemInfoMap.put(new ModelitemInfo("ref_model/@name", item), modelId);
                modelitemInfoMap.put(new ModelitemInfo("ref_model/modelitem/@name", item), item);
                modelitemInfoMap.put(new ModelitemInfo("ref_model/modelitem/@type", item), "number");
                modelitemInfoMap.put(new ModelitemInfo("ref_model/modelitem/@primaryKey", item), "true");

                modelitemInfoMap.put(new ModelitemInfo("model/modelitem/@__linenumber", item), 
                    Integer.toString(lineNumber++));

                repman.getModelMap().put(modelId, minfo);
                repman.saveModelMap(modelId);
                countOfMenu++;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}