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

かんがるーさんの日記

最近自分が興味をもったものを調べた時の手順等を書いています。今は Spring Boot をいじっています。

Spring Boot + Spring Integration でいろいろ試してみる ( その4 )( 監視しているディレクトリに置かれた Excel ファイルのデータを DB に登録する → 処理が終わったら Excel ファイルを削除/移動する )

概要

記事一覧はこちらです。

長いので2回に分けます。

参照したサイト・書籍

  1. JXLS
    http://jxls.sourceforge.net/

  2. Jxls Reader
    http://jxls.sourceforge.net/reference/reader.html

  3. Spring Framework Reference Documentation - 8. Resources
    http://docs.spring.io/spring/docs/current/spring-framework-reference/html/resources.html

目次

  1. 追加する機能の仕様をまとめる
  2. build.gradle を変更する
  3. Doma 2 を使うためのファイル・設定を追加し Dao, Entity を生成する
  4. Filter を作成する
  5. 動作確認
  6. Jxls Reader を利用して Excel ファイルのデータを読み込む処理を実装する
  7. 長くなり過ぎたので次回へ。。。

手順

追加する機能の仕様をまとめる

  • 現在以下の画像のフローですが、

    f:id:ksby:20160827022700p:plain

    filter と excelToDbChannel を追加して以下のフローに変更します。

    f:id:ksby:20160827023049p:plain

    ※画像は STS の integration-graph で描いています。

  • Filter を追加して、拡張子が .xlsx でなければ例外を throw して処理を異常終了させます。
    ※Filter は本来戻り値に boolean 型を返して Message を次の Channel に渡すか否かを判断するためのものですが、例外を throw しないと PseudoTransactionManager によるトランザクション処理が rollback しないので、今回は .xlsx でなければ例外を throw し、そうでなければ true を戻すように実装します。

  • excelToDbChannel を DirectChannel として追加します。DirectChannel にするのは checkFile –> filter –> process の一連の流れを1つのトランザクションとして処理するためです ( filter、process で例外が throw されるとファイルは error ディレクトリに移動されます )。
  • Excel ファイルは以下の画像のフォーマットとします。

    f:id:ksby:20160827083018p:plain

    • 項目は名前、パスワード、メールアドレス、権限の4個です。
    • 1行目はヘッダ行、2行目以降をデータ行とします。
    • 権限はカンマ区切りで複数記入可能とします。
  • process では Excel ファイルのデータを1行ずつ以下のように処理します。

    • 名前、パスワード、メールアドレスのデータを user_info テーブルに追加します。パスワードは追加持に Spring Security の BCryptPasswordEncoder#encode で暗号化します。
    • 権限のデータをカンマを区切り文字として分解して、権限の個数分 user_role テーブルに追加します。
  • Service Activator の process では Excel ファイルの全データが登録できたら commit し、できなかった場合には rollback されるようにします。

build.gradle を変更する

  1. feature/5-issue ブランチを作成します。

  2. build.gradle を リンク先のその1の内容 に変更します。

  3. Gradle projects View の左上にある「Refresh all Gradle projects」ボタンをクリックして build.gradle を反映します。

Doma 2 を使うためのファイル・設定を追加し Dao, Entity を生成する

  1. ksbysample-webapp-lending から以下のファイルをコピーします。

    ※→ のあるファイルは → に書かれた場所に配置します。

    • src/main/java/ksbysample/webapp/lending/config/DomaConfig.java
      → src/main/java/ksbysample/eipapp/dirchecker/config/DomaConfig.java
    • src/main/java/ksbysample/webapp/lending/util/doma/ComponentAndAutowiredDomaConfig.java
      → src/main/java/ksbysample/eipapp/dirchecker/util/doma/ComponentAndAutowiredDomaConfig.java
    • src/main/java/ksbysample/webapp/lending/util/doma/SelectOptionsUtils.java
      → src/main/java/ksbysample/eipapp/dirchecker/util/doma/SelectOptionsUtils.java
    • src/main/resources/log4jdbc.log4j2.properties
  2. src/main/resources の下に application.properties を作成し、リンク先の内容 の内容に変更します。

  3. src/main/java/ksbysample/eipapp/dirchecker の下の Application.javaリンク先の内容 に変更します。

  4. Gradle projects View から domaGen タスクを実行して Dao, Entity を生成します。

    f:id:ksby:20160827210150p:plain

  5. Gradle projects View から bootRun タスクを実行して正常に起動するか確認します。Doma 2 の SQL ファイル生成後はそのまま起動するとエラーが出る場合があるので、clean タスク実行 → Rebuild Project 実行をした後に bootRun タスクを実行します。

    f:id:ksby:20160827212321p:plain

    Tomcat が起動したことを確認できたら Ctrl+F2 を押して停止します。

Filter を作成する

  1. src/main/java/ksbysample/eipapp/dirchecker/eip/channel の下の ChannelConfig.javaリンク先の内容 に変更します。

  2. src/main/java/ksbysample/eipapp/dirchecker/eip/endpoint の下の InDirChecker.javaリンク先の内容 に変更します。

  3. src/main/java/ksbysample/eipapp/dirchecker/eip/endpoint の下の FileProcessor.javaリンク先の内容 に変更します。

動作確認

  1. Gradle projects View から bootRun を実行します。

    f:id:ksby:20160828004028p:plain

  2. C:\eipapp\ksbysample-eipapp-dirchecker\data\in に「テストファイル1.xlsx」というファイルを配置すると、ファイルがなくなりました。

    f:id:ksby:20160828004423p:plain ↓↓↓ f:id:ksby:20160828004611p:plain

  3. C:\eipapp\ksbysample-eipapp-dirchecker\data\in に「テストファイル2.txt」というファイルを配置すると、error ディレクトリに移動しました。

    f:id:ksby:20160828004939p:plain ↓↓↓ f:id:ksby:20160828005053p:plain

  4. Ctrl+F2 を押して Tomcat を停止します。

Jxls Reader を利用して Excel ファイルのデータを読み込む処理を実装する

Jxls ReaderExcel ファイルを処理するためには、

  • データ格納用クラスを作成します。
  • Excel ファイルとデータ格納用クラスの対応関係を記述した XML ファイルを作成します。
  • Java の実装を行います。

ですので、この順で実装します。

  1. src/main/java/ksbysample/eipapp/dirchecker の下に service.userinfo パッケージを作成します。

  2. src/main/java/ksbysample/eipapp/dirchecker/service/userinfo の下に UserInfoExcelRow.java を作成します。作成後、リンク先の内容 に変更します。

  3. getRoleListFromRoles メソッドの動作確認をしたいのでテストクラスを作成します。UserInfoExcelRow.java のソース上で Ctrl+Shift+T を押してコンテキストメニューを表示した後「Create New Test…」を選択します。「Create Test」ダイアログが表示されたら以下の画像の状態にした後、「OK」ボタンをクリックします。

    f:id:ksby:20160828205613p:plain

    「Choose Destination Directory」ダイアログが表示されたら「…\src\test\groovy...」の方を選択して「OK」ボタンをクリックします。

  4. src/test/groovy/ksbysample/eipapp/dirchecker/service/userinfo の下に UserInfoExcelRowTest.groovy が作成されますので、リンク先の内容 に変更します。

  5. 今の記述では groovy でコンパイルが出来なかったので build.gradle を リンク先のその2の内容 に変更します。

  6. テストを実行して全て成功することを確認します。

    f:id:ksby:20160828210150p:plain

  7. Excel ファイルとデータ格納用クラスの対応関係を記述した XML ファイルを作成します。src/main/resources の下に ksbysample/eipapp/dirchecker/service/userinfo ディレクトリを作成します。

  8. src/main/resources/ksbysample/eipapp/dirchecker/service/userinfo の下に userinfo-excel-cfg.xml を作成します。作成後、リンク先の内容 に変更します。

  9. 処理を行う Service クラスを実装します。src/main/java/ksbysample/eipapp/dirchecker/service/userinfo の下に UserInfoService.java を作成します。作成後、リンク先の内容 に変更します。

  10. UserInfoService#loadFromExcelToList の動作確認をしたいのでテストクラスを作成します。テスト用の Excel ファイルを src/test/resources の下に配置しますので、まずは src/test/resources の下に ksbysample/eipapp/dirchecker/service/userinfo ディレクトリを作成します。

  11. src/test/resources/ksbysample/eipapp/dirchecker/service/userinfo の下に以下の画像の Excel ファイルを TestData01.xlsx というファイル名で配置します。

    f:id:ksby:20160829011010p:plain

  12. テストで AssertJ が使いたいので build.gradle を リンク先のその3の内容 に変更します。

  13. Gradle projects View の左上にある「Refresh all Gradle projects」ボタンをクリックして build.gradle を反映します。

  14. UserInfoService.java のソース上で Ctrl+Shift+T を押してコンテキストメニューを表示した後「Create New Test…」を選択します。「Create Test」ダイアログが表示されたら以下の画像の状態にした後、「OK」ボタンをクリックします。

    f:id:ksby:20160829012529p:plain

    「Choose Destination Directory」ダイアログが表示されたら「…\src\test\java...」の方を選択して「OK」ボタンをクリックします。

  15. src/test/java/ksbysample/eipapp/dirchecker/service/userinfo の下に UserInfoServiceTest.java が作成されますので、リンク先の内容 に変更します。

  16. テストを実行して全て成功することを確認します。

    f:id:ksby:20160829022617p:plain

長くなり過ぎたので次回へ。。。

長くなり過ぎたので一旦区切ります。その2へ。

以下メモ書きです。

  • Spring Integration をやるなら STS の integration-graph は便利ですね ( というより必須? )。XML ファイルベースでやるなら、GUI で簡単に XML ファイルを作成できます。

ソースコード

build.gradle

■その1

group 'ksbysample'
version '1.1.0-RELEASE'

buildscript {
    ext {
        springBootVersion = '1.3.7.RELEASE'
    }
    repositories {
        jcenter()
        maven { url "http://repo.spring.io/repo/" }
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
        classpath("io.spring.gradle:dependency-management-plugin:0.6.0.RELEASE")
        // for Grgit
        classpath("org.ajoberstar:grgit:1.7.0")
    }
}

apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'idea'
apply plugin: 'spring-boot'
apply plugin: 'io.spring.dependency-management'
apply plugin: 'groovy'

sourceCompatibility = 1.8
targetCompatibility = 1.8

compileJava.options.compilerArgs = ['-Xlint:all']
compileTestGroovy.options.compilerArgs = ['-Xlint:all']
compileTestJava.options.compilerArgs = ['-Xlint:all']

eclipse {
    classpath {
        containers.remove('org.eclipse.jdt.launching.JRE_CONTAINER')
        containers 'org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8'
    }
}

idea {
    module {
        inheritOutputDirs = false
        outputDir = file("$buildDir/classes/main/")
    }
}

configurations {
    domaGenRuntime
}

repositories {
    jcenter()
}

dependencyManagement {
    imports {
        mavenBom 'io.spring.platform:platform-bom:2.0.7.RELEASE'
    }
}

dependencies {
    def jdbcDriver = "org.postgresql:postgresql:9.4.1209"

    // dependency-management-plugin によりバージョン番号が自動で設定されるもの
    // Appendix A. Dependency versions ( http://docs.spring.io/platform/docs/current/reference/htmlsingle/#appendix-dependency-versions ) 参照
    compile('org.springframework.boot:spring-boot-starter-integration')
    compile('org.springframework.integration:spring-integration-file')
    compile("org.springframework.boot:spring-boot-starter-security")
    compile("org.springframework.boot:spring-boot-starter-data-jpa")
    testCompile("org.springframework.boot:spring-boot-starter-test")
    testCompile("org.spockframework:spock-core") { exclude module: "groovy-all" }
    testCompile("org.spockframework:spock-spring") { exclude module: "groovy-all" }

    // dependency-management-plugin によりバージョン番号が自動で設定されないもの、あるいは最新バージョンを指定したいもの
    runtime("${jdbcDriver}")
    compile("org.seasar.doma:doma:2.12.1")
    compile("org.bgee.log4jdbc-log4j2:log4jdbc-log4j2-jdbc4.1:1.16")
    compile("org.projectlombok:lombok:1.16.10")
    compile("org.apache.commons:commons-lang3:3.4")
    compile("org.jxls:jxls-reader:2.0.2")

    // for Doma-Gen
    domaGenRuntime("org.seasar.doma:doma-gen:2.12.1")
    domaGenRuntime("${jdbcDriver}")
}

// for Doma-Gen
task domaGen << {
    // まず変更が必要なもの
    def rootPackageName  = 'ksbysample.eipapp.dirchecker'
    def daoPackagePath   = 'src/main/java/ksbysample/eipapp/dirchecker/dao'
    def dbUrl            = 'jdbc:postgresql://localhost/ksbylending'
    def dbUser           = 'ksbylending_user'
    def dbPassword       = 'xxxxxxxx'
    def tableNamePattern = '.*'
    // おそらく変更不要なもの
    def importOfComponentAndAutowiredDomaConfig = "${rootPackageName}.util.doma.ComponentAndAutowiredDomaConfig"
    def workDirPath      = 'work'
    def workDaoDirPath   = "${workDirPath}/dao"

    // 作業用ディレクトリを削除する
    clearDir("${workDirPath}")

    // 現在の Dao インターフェースのバックアップを取得する
    copy() {
        from "${daoPackagePath}"
        into "${workDaoDirPath}/org"
    }

    // Dao インターフェース、Entity クラスを生成する
    ant.taskdef(resource: 'domagentask.properties',
            classpath: configurations.domaGenRuntime.asPath)
    ant.gen(url: "${dbUrl}", user: "${dbUser}", password: "${dbPassword}", tableNamePattern: "${tableNamePattern}") {
        entityConfig(packageName: "${rootPackageName}.entity", useListener: false)
        daoConfig(packageName: "${rootPackageName}.dao")
        sqlConfig()
    }

    // 生成された Dao インターフェースを作業用ディレクトリにコピーし、
    // @ComponentAndAutowiredDomaConfig アノテーションを付加する
    copy() {
        from "${daoPackagePath}"
        into "${workDaoDirPath}/replace"
        filter {
            line -> line.replaceAll('import org.seasar.doma.Dao;', "import ${importOfComponentAndAutowiredDomaConfig};\nimport org.seasar.doma.Dao;")
                    .replaceAll('@Dao', '@Dao\n@ComponentAndAutowiredDomaConfig')
        }
    }

    // @ComponentAndAutowiredDomaConfig アノテーションを付加した Dao インターフェースを
    // dao パッケージへ戻す
    copy() {
        from "${workDaoDirPath}/replace"
        into "${daoPackagePath}"
    }

    // 元々 dao パッケージ内にあったファイルを元に戻す
    copy() {
        from "${workDaoDirPath}/org"
        into "${daoPackagePath}"
    }

    // 作業用ディレクトリを削除する
    clearDir("${workDirPath}")

    // 自動生成したファイルを git add する
    addGit()
}

/* -----------------------------------------------------------------------------
 * メソッド定義部
 ---------------------------------------------------------------------------- */
void clearDir(String dirPath) {
    delete dirPath
}

void addGit() {
    def grgit = org.ajoberstar.grgit.Grgit.open(dir: project.projectDir.parent)
    grgit.add(patterns: ['.'])
}
  • version を 1.0.0-RELEASE1.1.0-RELEASE へ変更します。
  • buildscript に classpath("org.ajoberstar:grgit:1.7.0") を追加します。
  • configurations { domaGenRuntime } を追加します。
  • dependencies に以下の記述を追加します。
    • def jdbcDriver = "org.postgresql:postgresql:9.4.1209"
    • compile("org.springframework.boot:spring-boot-starter-security")
    • compile("org.springframework.boot:spring-boot-starter-data-jpa")
    • runtime("${jdbcDriver}")
    • compile("org.seasar.doma:doma:2.12.1")
    • compile("org.bgee.log4jdbc-log4j2:log4jdbc-log4j2-jdbc4.1:1.16")
    • compile("org.jxls:jxls-reader:2.0.2")
    • domaGenRuntime("org.seasar.doma:doma-gen:2.12.1")
    • domaGenRuntime("${jdbcDriver}")
  • task domaGen << { ... } を追加します。
  • clearDir メソッドを追加します。
  • addGit メソッドを追加します。open するディレクトリの指定は project.projectDirproject.projectDir.parent へ変更します。

■その2

dependencies {
    def jdbcDriver = "org.postgresql:postgresql:9.4.1209"

    // dependency-management-plugin によりバージョン番号が自動で設定されるもの
    // Appendix A. Dependency versions ( http://docs.spring.io/platform/docs/current/reference/htmlsingle/#appendix-dependency-versions ) 参照
    compile('org.springframework.boot:spring-boot-starter-integration')
    compile('org.springframework.integration:spring-integration-file')
    compile("org.springframework.boot:spring-boot-starter-security")
    compile("org.springframework.boot:spring-boot-starter-data-jpa")
    testCompile("org.springframework.boot:spring-boot-starter-test")
    testCompile("org.spockframework:spock-core")
    testCompile("org.spockframework:spock-spring")

    // dependency-management-plugin によりバージョン番号が自動で設定されないもの、あるいは最新バージョンを指定したいもの
    runtime("${jdbcDriver}")
    compile("org.seasar.doma:doma:2.12.1")
    compile("org.bgee.log4jdbc-log4j2:log4jdbc-log4j2-jdbc4.1:1.16")
    compile("org.projectlombok:lombok:1.16.10")
    compile("org.apache.commons:commons-lang3:3.4")
    compile("org.jxls:jxls-reader:2.0.2")

    // for Doma-Gen
    domaGenRuntime("org.seasar.doma:doma-gen:2.12.1")
    domaGenRuntime("${jdbcDriver}")
}
  • spock-core, spock-spring の後の { exclude module: "groovy-all" } の記述を削除します。

■その3

dependencies {
    def jdbcDriver = "org.postgresql:postgresql:9.4.1209"

    // dependency-management-plugin によりバージョン番号が自動で設定されるもの
    // Appendix A. Dependency versions ( http://docs.spring.io/platform/docs/current/reference/htmlsingle/#appendix-dependency-versions ) 参照
    compile('org.springframework.boot:spring-boot-starter-integration')
    compile('org.springframework.integration:spring-integration-file')
    compile("org.springframework.boot:spring-boot-starter-security")
    compile("org.springframework.boot:spring-boot-starter-data-jpa")
    testCompile("org.springframework.boot:spring-boot-starter-test")
    testCompile("org.spockframework:spock-core")
    testCompile("org.spockframework:spock-spring")

    // dependency-management-plugin によりバージョン番号が自動で設定されないもの、あるいは最新バージョンを指定したいもの
    runtime("${jdbcDriver}")
    compile("org.seasar.doma:doma:2.12.1")
    compile("org.bgee.log4jdbc-log4j2:log4jdbc-log4j2-jdbc4.1:1.16")
    compile("org.projectlombok:lombok:1.16.10")
    compile("org.apache.commons:commons-lang3:3.4")
    compile("org.jxls:jxls-reader:2.0.2")
    testCompile("org.assertj:assertj-core:3.5.2")

    // for Doma-Gen
    domaGenRuntime("org.seasar.doma:doma-gen:2.12.1")
    domaGenRuntime("${jdbcDriver}")
}
  • dependencies に testCompile("org.assertj:assertj-core:3.5.2") を追加します。

application.properties

hibernate.dialect=org.hibernate.dialect.PostgreSQL9Dialect
doma.dialect=org.seasar.doma.jdbc.dialect.PostgresDialect

spring.datasource.url=jdbc:log4jdbc:postgresql://localhost/ksbylending
spring.datasource.username=ksbylending_user
spring.datasource.password=xxxxxxxx
spring.datasource.driverClassName=net.sf.log4jdbc.sql.jdbcapi.DriverSpy
spring.datasource.initial-size=1

Application.java

package ksbysample.eipapp.dirchecker;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration;
import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration;

@SpringBootApplication(exclude = {JpaRepositoriesAutoConfiguration.class, HibernateJpaAutoConfiguration.class})
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

}
  • @SpringBootApplication@SpringBootApplication(exclude = {JpaRepositoriesAutoConfiguration.class, HibernateJpaAutoConfiguration.class}) へ変更します。

ChannelConfig.java

package ksbysample.eipapp.dirchecker.eip.channel;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.messaging.MessageChannel;

@Configuration
public class ChannelConfig {

    @Bean
    public MessageChannel inChannel() {
        return new DirectChannel();
    }

    @Bean
    public MessageChannel excelToDbChannel() {
        return new DirectChannel();
    }

}
  • @Bean public MessageChannel excelToDbChannel() { ... } を追加します。

InDirChecker.java

package ksbysample.eipapp.dirchecker.eip.endpoint;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.integration.annotation.Filter;
import org.springframework.integration.annotation.InboundChannelAdapter;
import org.springframework.integration.annotation.MessageEndpoint;
import org.springframework.integration.annotation.Poller;
import org.springframework.integration.channel.NullChannel;
import org.springframework.integration.file.FileReadingMessageSource;
import org.springframework.integration.scheduling.PollerMetadata;
import org.springframework.integration.transaction.DefaultTransactionSynchronizationFactory;
import org.springframework.integration.transaction.ExpressionEvaluatingTransactionSynchronizationProcessor;
import org.springframework.integration.transaction.PseudoTransactionManager;
import org.springframework.integration.transaction.TransactionSynchronizationFactory;
import org.springframework.messaging.Message;
import org.springframework.scheduling.support.PeriodicTrigger;
import org.springframework.transaction.interceptor.DefaultTransactionAttribute;
import org.springframework.transaction.interceptor.MatchAlwaysTransactionAttributeSource;
import org.springframework.transaction.interceptor.TransactionInterceptor;

import java.io.File;
import java.nio.file.Paths;
import java.util.Collections;

@MessageEndpoint
public class InDirChecker {

    ..........

    @InboundChannelAdapter(value = "inChannel", poller = @Poller("checkFilePoller"))
    public Message<File> checkFile() {
        return inDirFileReadingMessageSource.receive();
    }

    @Filter(inputChannel = "inChannel", outputChannel = "excelToDbChannel")
    public boolean filter(Message<File> message) {
        File file = message.getPayload();
        if (!StringUtils.endsWith(file.getName(), ".xlsx")) {
            throw new RuntimeException(
                    String.format("拡張子が .xlsx のファイルではありません ( %s )。", file.getName()));
        }
        return true;
    }

}
  • @Filter(inputChannel = "inChannel", outputChannel = "excelToDbChannel") public boolean filter(Message<File> message) { ... } を追加します。

FileProcessor.java

package ksbysample.eipapp.dirchecker.eip.endpoint;

import org.springframework.integration.annotation.MessageEndpoint;
import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.messaging.Message;

import java.io.File;

@MessageEndpoint
public class FileProcessor {

    @ServiceActivator(inputChannel = "excelToDbChannel")
    public void process(Message<File> message) throws Exception {
        File file = message.getPayload();
        System.out.println(file.getAbsolutePath());
    }

}
  • @ServiceActivator(inputChannel = "inChannel")@ServiceActivator(inputChannel = "excelToDbChannel") に変更します。

UserInfoExcelRow.java

package ksbysample.eipapp.dirchecker.service.userinfo;

import lombok.Data;
import org.apache.commons.lang3.StringUtils;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;

@Data
public class UserInfoExcelRow {

    private String username;

    private String password;

    private String mailAddress;

    private String roles;

    public List<String> getRoleListFromRoles() {
        List<String> result = Collections.EMPTY_LIST;
        if (StringUtils.isNotBlank(this.roles)) {
            String[] roleArray = roles.split(",");
            result = Arrays.asList(roleArray);
        }
        return result;
    }

}

UserInfoExcelRowTest.groovy

package ksbysample.eipapp.dirchecker.service.userinfo

import spock.lang.Specification
import spock.lang.Unroll

class UserInfoExcelRowTest extends Specification {

    @Unroll
    def "GetRoleListFromRoles(#roles) --> #result"() {
        given:
        UserInfoExcelRow userInfoExcelRow = new UserInfoExcelRow()
        userInfoExcelRow.roles = roles

        expect:
        userInfoExcelRow.getRoleListFromRoles() == result

        where:
        roles                  || result
        null                   || []
        ""                     || []
        "ROLE_USER"            || ["ROLE_USER"]
        "ROLE_USER,ROLE_ADMIN" || ["ROLE_USER", "ROLE_ADMIN"]
    }

}

userinfo-excel-cfg.xml

<?xml version="1.0" encoding="UTF-8"?>
<workbook>
    <worksheet name="Sheet1">
        <section startRow="0" endRow="0">
        </section>
        <loop startRow="1" endRow="1" items="userInfoExcelRowList" var="userInfoExcelRow"
              varType="ksbysample.eipapp.dirchecker.service.userinfo.UserInfoExcelRow">
            <section startRow="1" endRow="1">
                <mapping row="1" col="0">userInfoExcelRow.username</mapping>
                <mapping row="1" col="1">userInfoExcelRow.password</mapping>
                <mapping row="1" col="2">userInfoExcelRow.mailAddress</mapping>
                <mapping row="1" col="3">userInfoExcelRow.roles</mapping>
            </section>
            <loopbreakcondition>
                <rowcheck offset="0">
                    <cellcheck offset="0"></cellcheck>
                </rowcheck>
            </loopbreakcondition>
        </loop>
    </worksheet>
</workbook>

UserInfoService.java

package ksbysample.eipapp.dirchecker.service.userinfo;

import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.jxls.reader.ReaderBuilder;
import org.jxls.reader.XLSReadStatus;
import org.jxls.reader.XLSReader;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Service;
import org.xml.sax.SAXException;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Service
public class UserInfoService {

    private static final String CLASSPATH_USERINFO_EXCEL_CFG_XML
            = "ksbysample/eipapp/dirchecker/service/userinfo/userinfo-excel-cfg.xml";

    public List<UserInfoExcelRow> loadFromExcelToList(File excelFile)
            throws IOException, SAXException, InvalidFormatException {
        Resource rsExcelCfgXml = new ClassPathResource(CLASSPATH_USERINFO_EXCEL_CFG_XML);
        Resource rsUserInfoExcel = new FileSystemResource(excelFile.getAbsolutePath());

        XLSReader reader = ReaderBuilder.buildFromXML(rsExcelCfgXml.getFile());

        List<UserInfoExcelRow> userInfoExcelRowList = new ArrayList<>();
        Map<String, Object> beans = new HashMap<>();
        beans.put("userInfoExcelRow", new UserInfoExcelRow());
        beans.put("userInfoExcelRowList", userInfoExcelRowList);

        try (InputStream isUserInfoExcel = new BufferedInputStream(rsUserInfoExcel.getInputStream())) {
            XLSReadStatus status = reader.read(isUserInfoExcel, beans);
        }

        return userInfoExcelRowList;
    }

}

UserInfoServiceTest.java

package ksbysample.eipapp.dirchecker.service.userinfo;

import ksbysample.eipapp.dirchecker.Application;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import java.util.List;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.tuple;


@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = Application.class)
public class UserInfoServiceTest {

    private static final String CLASSPATH_EXCEL_FOR_TEST
            = "ksbysample/eipapp/dirchecker/service/userinfo/Book1.xlsx";

    @Autowired
    private UserInfoService userInfoService;

    @Test
    public void loadFromExcelToList() throws Exception {
        Resource resource = new ClassPathResource(CLASSPATH_EXCEL_FOR_TEST);
        List<UserInfoExcelRow> userInfoExcelRowList = userInfoService.loadFromExcelToList(resource.getFile());
        assertThat(userInfoExcelRowList).hasSize(2);
        assertThat(userInfoExcelRowList).extracting("username", "password", "mailAddress", "roles")
                .containsOnly(tuple("yota takahashi", "12345678", "yota.takahashi@test.co.jp", "ROLE_USER")
                        , tuple("aoi inoue", "abcdefgh", "aoi.inoue@sample.com", "ROLE_ADMIN,ROLE_USER"));
    }

}

履歴

2016/08/29
初版発行。
2016/08/29
* build.gradle から { exclude module: "groovy-all" } を削除する記述を追加しました。