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

かんがるーさんの日記

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

Spring Boot 1.3.x の Web アプリを 1.4.x へバージョンアップする ( その7 )( Google の Java コンパイル時バグチェックツール? Error Prone を試してみる )

概要

記事一覧はこちらです。

Spring Boot 1.3.x の Web アプリを 1.4.x へバージョンアップする ( その6 )( 「Run ‘All Tests’ with Coverage」実行時のエラーを解消する+build タスク実行時の警告を解消する ) の続きです。

  • 今回の手順で確認できるのは以下の内容です。
    • GoogleJava コンパイル時バグチェックツール? ( 静的解析ツールではないらしい ) Error Prone の導入方法・使い方を調べて、ksbysample-webapp-lending に導入するとどのような結果が出るのかを試してみます。
    • IntelliJ IDEA の Error Prone の Plugin もあるようなのでインストールして試してみます。

参照したサイト・書籍

  1. google/error-prone
    https://github.com/google/error-prone

  2. tbroyer/gradle-errorprone-plugin
    https://github.com/tbroyer/gradle-errorprone-plugin

  3. error-prone/examples/gradle/build.gradle
    https://github.com/google/error-prone/blob/master/examples/gradle/build.gradle

    • build.gradle に Error Prone を導入する時のサンプルです。
  4. How To Find Bugs, Part 1: A Minimal Bug Detector
    https://www.lmax.com/blog/staff-blogs/2016/04/01/find-bugs-part-1-minimal-bug-detector/

  5. guava - warning: Cannot find annotation method - Warnings as errors causes builds to fail
    https://github.com/robolectric/robolectric/issues/2446

  6. Adding the gradle-errorprone-plugin causes “bad path element” warnings, need to add “-Xlint:-path” to compilerArgs
    https://github.com/tbroyer/gradle-errorprone-plugin/issues/15

目次

  1. Error Prone を導入してみる
    1. Error Prone とは?
    2. build.gradle を変更する
    3. build タスクを実行する
    4. ClassNewInstance の警告を修正する
    5. MissingOverride の警告を修正する
    6. GetClassOnClass のエラーを修正する
    7. 再び build タスクを実行するも、なぜかまだエラーが。。。
    8. MissingOverride の警告を修正する
    9. 警告: タイプ'GuardedBy'内に注釈メソッド'value()'が見つかりません: javax.annotation.concurrent.GuardedByのクラス・ファイルが見つかりません の警告を修正する
    10. Finally の警告を修正する
    11. GetClassOnAnnotation のエラーを修正する
    12. 再び build タスクを実行するもまだエラーが出ます
    13. 警告: [path] 不正なパス要素"C:\project-springboot\ksbysample-webapp-lending\build\resources\main": そのファイルまたはディレクトリはありません の警告を修正する
    14. BoxedPrimitiveConstructor の警告を修正する
    15. MissingOverride の警告を修正する
    16. 再び build タスクを実行し、やっと警告もエラーも出なくなりました
  2. Error-prone Compiler Integration Plugin を導入してみる
    1. Error-prone Compiler Integration Plugin をインストールする
    2. Rebuild Project を実行する
    3. TypeParameterUnusedInFormals の警告を修正する
  3. 次回は。。。

手順

Error Prone を導入してみる

Error Prone とは?

  • Google の バグチェックツール。
  • バージョン 2.0.6 以降は Java 1.8 以降でしか動作しません。
  • ソースコードを静的解析するのではなく、Java コンパイラの機能で抽象構文ツリー (Abstract Syntax Tree、AST) にしてチェックするらしいです。
  • 検出できるバグの種類は FindBugs と比較すると少ないですが、plugin を作成して追加できます。
  • 検出できるバグの一覧は Bug patterns のページに記載されています。詳細ページには説明以外にサンプルコードも書かれており、内容が分かりやすいです。
  • FindBugs 3.0.1 では検出できない Files.lines(...) の try-with-resources 構文使用漏れが検出できます。ちなみに FindBugs で検出させるための記事が How To Find Bugs, Part 1: A Minimal Bug Detector にありました。
  • ClassNewInstance を見ると “deprecation in JDK 9” という記述も。JDK 9 も見据えて使用すべきではないコードも検出できるようです。

build.gradle を変更する

  1. error-prone/examples/gradle/build.gradle を参考にして build.gradle を リンク先のその1の内容 に変更します。

  2. 変更後、Gradle Tool Window の左上にある「Refresh all Gradle projects」ボタンをクリックして更新します。

build タスクを実行する

  1. clean タスク → Rebuild Project → build タスク の順に実行します。

    build タスクが実行されるといろいろダウンロードされます。

    f:id:ksby:20170217013643p:plain

    その後コンパイルエラーが出力されて、最後に “BUILD FAILED” が出力されました。結構ありますね。。。

    f:id:ksby:20170217015028p:plain f:id:ksby:20170217015152p:plain

    ソースファイルと行数、エラーか警告か、エラーの種類 ( [ClassNewInstance] 等 )、エラーと判定されたソースの位置が出力されます。

    またエラーの詳細は各エラー毎に出力されている (see http://errorprone.info/bugpattern/...) のリンクをクリックすると Web ページが表示されて確認できます。かなり分かりやすいです。

    出力されたエラーはエラー1個、警告9個で、種類は以下の3種類でした。

ClassNewInstance の警告を修正する

Class#newInstance は問題があって JDK 9 から非推奨になるらしいので修正します。

  1. src/main/java/ksbysample/webapp/lending/config の下の DomaConfig.javaリンク先の内容 に変更します。

  2. src/main/java/ksbysample/webapp/lending/util/cookie の下の CookieUtils.javaリンク先の内容 に変更します。

MissingOverride の警告を修正する

Values オブジェクトはかなりトリッキーなことをしているので @SuppressWarnings("MissingOverride") を付けて警告を回避します。

  1. src/main/java/ksbysample/webapp/lending/values/lendingapp の下の LendingAppStatusValues.javaリンク先の内容 に変更します。

  2. src/main/java/ksbysample/webapp/lending/values/lendingbook の下の LendingBookApprovalResultValues.javaリンク先の内容 に変更します。

  3. src/main/java/ksbysample/webapp/lending/values/lendingbook の下の LendingBookLendingAppFlgValues.javaリンク先の内容 に変更します。

GetClassOnClass のエラーを修正する

こちらは単純なミスでした。

  1. src/main/java/ksbysample/webapp/lending/values/validation の下の ValuesEnumValidator.javaリンク先の内容 に変更します。

再び build タスクを実行するも、なぜかまだエラーが。。。

  1. これでエラーが全て解消されたはずなので、再び clean タスク → Rebuild Project → build タスク の順に実行します。

    が、なぜかまた大量にエラーが出ました。エラーの数が一定数を超えるとそれ以上のエラーは出なくなるのでしょうか?

    f:id:ksby:20170217233647p:plain

    出力されたエラーはエラー3個、警告12個で、種類は以下の4種類でした。

MissingOverride の警告を修正する

単純な @Override つけ忘れでした。

  1. src/main/java/ksbysample/common/test/helper の下の SimpleRequestBuilder.javaリンク先の内容 に変更します。

警告: タイプ'GuardedBy'内に注釈メソッド'value()'が見つかりません: javax.annotation.concurrent.GuardedByのクラス・ファイルが見つかりません の警告を修正する

Web で検索したら guava - warning: Cannot find annotation method - Warnings as errors causes builds to fail という GitHub の Issue が見つかりました。testCompile 'com.google.code.findbugs:jsr305:1.3.9' を付ければ警告が消えると書いてありますので、同じように対応します。。

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

  2. 変更後、Gradle Tool Window の左上にある「Refresh all Gradle projects」ボタンをクリックして更新します。

Finally の警告を修正する

finally 内で throw していることが原因による警告です。テスト用のクラスでエラーを検出する方を優先したいので @SuppressWarnings("Finally") を付けて警告が出ないようにします。

  1. src/test/java/ksbysample/common/test/rule/db の下の TestDataResource.javaリンク先の内容 に変更します。

GetClassOnAnnotation のエラーを修正する

getClass() ではなく annotationType() を使え、というエラーでした。annotationType() は知りませんでしたね。

  1. src/test/java/ksbysample/common/test/rule/db の下の TestSqlExecutor.javaリンク先の内容 に変更します。

再び build タスクを実行するもまだエラーが出ます

  1. 出ていたエラーを解消したので、再び clean タスク → Rebuild Project → build タスク の順に実行します。

    が、まだエラーが出ますね。見た感じ、軽微そうな警告のみになってきました。

    f:id:ksby:20170218023841p:plain

  2. 出力されたエラーは警告6個で、種類は以下の3種類でした。最初の “[path] 不正なパス要素…” は最初から出ていたのですが、"(see http://errorprone.info/bugpattern/…)“ が出力されていなかったのでスルーしていました。今回はこちらも修正します。

警告: [path] 不正なパス要素"C:\project-springboot\ksbysample-webapp-lending\build\resources\main": そのファイルまたはディレクトリはありません の警告を修正する

Web で検索したら Adding the gradle-errorprone-plugin causes “bad path element” warnings, need to add “-Xlint:-path” to compilerArgs という GitHub の Issue が見つかりました。Java コンパイル時の -Xlint オプションに -path を付ければよいようです。

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

BoxedPrimitiveConstructor の警告を修正する

new Long(...)JDK 9 から非推奨になるので Long.valueOf(...) を使った方がよいという警告です。

  1. src/main/java/ksbysample/webapp/lending/service/queue の下の InquiringStatusOfBookQueueServiceTest.javaリンク先の内容 に変更します。

  2. src/main/java/ksbysample/webapp/lending/web/booklist の下の BooklistServiceTest.javaリンク先の内容 に変更します。

MissingOverride の警告を修正する

こちらは前と同じでした。@SuppressWarnings("MissingOverride") を付けて警告を回避します。

  1. src/test/java/ksbysample/webapp/lending/values/validation の下の ValuesEnumValidatorTest.javaリンク先の内容 に変更します。

再び build タスクを実行し、やっと警告もエラーも出なくなりました

  1. 出ていたエラーを解消したので、再び clean タスク → Rebuild Project → build タスク の順に実行します。

    今度は1つも警告、エラーが出ずに “BUILD SUCCESSFUL” の文字が出力されました。FindBugs より検出できるバグは少ないと聞いていましたが、結構検出されましたね。。。 また JDK 9 から非推奨になる部分に警告が出て、修正内容も Bug patterns のページで分かるのはかなり良さそうな感触でした。

    f:id:ksby:20170218082334p:plain

    Error Prone は個人的にはかなり気に入りましたので、今後は積極的に入れていきたいと思います。

Error-prone Compiler Integration Plugin を導入してみる

Error-prone Compiler Integration Plugin をインストールする

  1. IntelliJ IDEA のメインメニューから「File」-「Settings…」を選択します。

  2. 「Settings」ダイアログが表示されます。画面左側のリストから「Plugins」を選択した後、画面中央下の「Browse repositories…」ボタンをクリックします。

    f:id:ksby:20170218095447p:plain

  3. 「Browse Repositories」ダイアログが表示されます。画面左上の検索フィールドに “Error-prone” と入力すると「Error-prone Compiler Integration」が表示されますので、選択して「Install」ボタンをクリックします。

    f:id:ksby:20170218095954p:plain

    プラグインがダウンロードされて「Install」ボタンが「Restart IntelliJ IDEA」ボタンに切り替わりますのでクリックします。

  4. 「Settings」ダイアログに戻りますので「OK」ボタンをクリックします。

    「Platform and Plugin Updates」ダイアログが表示されますので「Restart」ボタンをクリックします。

    f:id:ksby:20170218100155p:plain

  5. IntelliJ IDEA が再起動します。再起動しただけではまだ有効になっていません。設定を変更します。

  6. 再度 IntelliJ IDEA のメインメニューから「File」-「Settings…」を選択します。

  7. 「Settings」ダイアログが表示されます。画面左上の検索フィールドに “java compiler” と入力した後、画面左側のリストから「Java Compiler」を選択して、画面中央上の「Use compiler」で “Javac” → “Javac with error-prone” へ変更します。変更後「OK」ボタンをクリックしてダイアログを閉じます。

    f:id:ksby:20170218101044p:plain

    以上でインストール、設定は完了です。

Rebuild Project を実行する

  1. IntelliJ IDEA のメインメニューから「Build」-「Rebuild Project」を選択します。

    gradle からのコンパイル時エラーは全て解消したので大丈夫だろうと思っていたら、1件だけ警告が出ました。

    f:id:ksby:20170218103157p:plain

    出た警告は以下のものでした。

TypeParameterUnusedInFormals の警告を修正する

警告が出たのは src/main/java/ksbysample/webapp/lending/webapi/common の下の CommonWebApiResponse.java で、

@Data
public class CommonWebApiResponse<T> {

    private int errcode = 0;

    private String errmsg = "";

    private T content;

}

という書き方だと private T content; の T は型チェックが機能しないらしいです。きちんと型チェックを機能させるためには以下のように書くべきらしいですが、このクラスは Jackson が JSON に自動変換する際に使用するためのもので以下の実装にしても public <T> T getContent(Class<T> clazz) メソッドは呼び出されないので、@SuppressWarnings("TypeParameterUnusedInFormals") を付加して回避することにします。

@Data
public class CommonWebApiResponse<T> {

    private int errcode = 0;

    private String errmsg = "";

    private T content;

    public <T> T getContent(Class<T> clazz) {
        return clazz.cast(this.content);
    }

}
  1. src/main/java/ksbysample/webapp/lending/webapi/common の下の CommonWebApiResponse.javaリンク先の内容 に変更します。

  2. IntelliJ IDEA のメインメニューから「Build」-「Rebuild Project」を選択して実行すると今度はエラー、警告は1件も出ませんでした。

次回は。。。

コードチェックツールに興味が湧いたので、IntelliJ IDEA の Plugin である CheckStyle-IDEA, FindBugs-IDEA の導入、及び build.gradle への checkstyle の導入をしてみます。

ソースコード

build.gradle

■その1

buildscript {
    ext {
        springBootVersion = '1.4.4.RELEASE'
    }
    repositories {
        jcenter()
        maven { url "http://repo.spring.io/repo/" }
        maven { url "https://plugins.gradle.org/m2/" }
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
        classpath("io.spring.gradle:dependency-management-plugin:0.6.1.RELEASE")
        // for Error Prone ( http://errorprone.info/ )
        classpath("net.ltgt.gradle:gradle-errorprone-plugin:0.0.8")
        // for Grgit
        classpath("org.ajoberstar:grgit:1.8.0")
        // Gradle Download Task
        classpath("de.undercouch:gradle-download-task:3.2.0")
    }
}

apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'idea'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'
apply plugin: 'de.undercouch.download'
apply plugin: 'groovy'
apply plugin: 'net.ltgt.errorprone'

..........

configurations {
    // for Doma 2
    domaGenRuntime

    // for Error Prone ( http://errorprone.info/ )
    errorprone {
        resolutionStrategy.force 'com.google.errorprone:error_prone_core:2.0.15'
    }
}

repositories {
    jcenter()
}
  • buildscript の以下の点を変更します。
    • repositories に maven { url "https://plugins.gradle.org/m2/" } を追加します。
    • dependencies に classpath("net.ltgt.gradle:gradle-errorprone-plugin:0.0.8") を追加します。
  • apply plugin: 'net.ltgt.errorprone' を追加します。
  • configurations に errorprone { resolutionStrategy.force 'com.google.errorprone:error_prone_core:2.0.15' } を追加します。

■その2

dependencies {
    ..........

    // dependency-management-plugin によりバージョン番号が自動で設定されないもの、あるいは最新バージョンを指定したいもの
    ..........
    testCompile("org.spockframework:spock-core:${spockVersion}") {
        exclude module: "groovy-all"
    }
    testCompile("org.spockframework:spock-spring:${spockVersion}") {
        exclude module: "groovy-all"
    }
    testCompile("com.google.code.findbugs:jsr305:3.0.1")
  • testCompile("com.google.code.findbugs:jsr305:3.0.1") を追加します。

■その3

[compileJava, compileTestGroovy, compileTestJava]*.options*.compilerArgs = ['-Xlint:all,-options,-processing,-path']
  • ,-path を追加します。

DomaConfig.java

@Component
public class DomaConfig implements Config {

    ..........

    @Autowired
    public void setDialect(@Value("${doma.dialect}") String domaDialect)
            throws ClassNotFoundException, IllegalAccessException, InstantiationException
            , NoSuchMethodException, InvocationTargetException {
        this.dialect = (Dialect) Class.forName(domaDialect).getConstructor().newInstance();
    }
  • Class.forName(domaDialect).newInstance();Class.forName(domaDialect).getConstructor().newInstance(); に変更します。
  • setDialect メソッドの throws に , NoSuchMethodException, InvocationTargetException を追加します。

CookieUtils.java

public class CookieUtils {

    public static <T extends CookieGenerator> void addCookie(Class<T> clazz, HttpServletResponse response, String cookieValue) {
        try {
            T cookieGenerator = clazz.getConstructor().newInstance();
            cookieGenerator.addCookie(response, cookieValue);
        } catch (InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
            throw new RuntimeException(e);
        }
    }

    public static <T extends CookieGenerator> void removeCookie(Class<T> clazz, HttpServletResponse response) {
        try {
            T cookieGenerator = clazz.getConstructor().newInstance();
            cookieGenerator.removeCookie(response);
        } catch (InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
            throw new RuntimeException(e);
        }
    }
  • addCookie, removeCookie メソッド内の以下の点を変更します。
    • clazz.newInstance();clazz.getConstructor().newInstance(); へ変更します。
    • catch に列挙する例外に | NoSuchMethodException | InvocationTargetException を追加します。

LendingAppStatusValues.java

@SuppressWarnings("MissingOverride")
@Getter
@AllArgsConstructor
public enum LendingAppStatusValues implements Values {
  • @SuppressWarnings("MissingOverride") を追加します。

LendingBookApprovalResultValues.java

@SuppressWarnings("MissingOverride")
@Getter
@AllArgsConstructor
public enum LendingBookApprovalResultValues implements Values {
  • @SuppressWarnings("MissingOverride") を追加します。

LendingBookLendingAppFlgValues.java

@SuppressWarnings("MissingOverride")
@Getter
@AllArgsConstructor
public enum LendingBookLendingAppFlgValues implements Values {
  • @SuppressWarnings("MissingOverride") を追加します。

ValuesEnumValidator.java

public class ValuesEnumValidator implements ConstraintValidator<ValuesEnum, String> {

    ..........

    @Override
    public void initialize(ValuesEnum constraintAnnotation) {
        this.enumClass = constraintAnnotation.enumClass();
        this.allowEmpty = constraintAnnotation.allowEmpty();

        // enumClass 属性に Values インターフェースを実装していない列挙型が指定されている場合にはエラーにする
        try {
            if (!Values.class.isAssignableFrom(Class.forName(this.enumClass.getName()))) {
                throw new RuntimeException(
                        MessageFormat.format("enumClass 属性に Values インターフェースを実装した列挙型が指定されていません ( {0} )"
                                , this.enumClass.getName()));
            }
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
    }
  • MessageFormat.format(...)this.enumClass.getClass()this.enumClass.getName() に変更します。

SimpleRequestBuilder.java

public class SimpleRequestBuilder implements RequestBuilder {

    private final MockHttpServletRequest request;

    public SimpleRequestBuilder(MockHttpServletRequest request) {
        this.request = request;
    }

    @Override
    public MockHttpServletRequest buildRequest(ServletContext servletContext) {
        return request;
    }

}
  • buildRequest メソッドに @Override を付加します。

TestDataResource.java

@Component
public class TestDataResource extends TestWatcher {

    ..........

    @SuppressWarnings("Finally")
    @Override
    protected void finished(Description description) {
  • finished メソッドに @SuppressWarnings("Finally") を付加します。

TestSqlExecutor.java

public class TestSqlExecutor<L extends Annotation, I extends Annotation> {

    ..........

    @SuppressWarnings("unchecked")
    private I[] value(L testSqlList) {
        try {
            Method method = testSqlList.annotationType().getMethod("value");
            return (I[]) method.invoke(testSqlList);
        } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }

    private long order(I testSql) {
        try {
            Method method = testSql.annotationType().getMethod("order");
            return (long) method.invoke(testSql);
        } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }

    private String sql(I testSql) {
        try {
            Method method = testSql.annotationType().getMethod("sql");
            return (String) method.invoke(testSql);
        } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }

}
  • value メソッド内の testSqlList.getClass().getMethod("value");testSqlList.annotationType().getMethod("value"); へ変更しました。
  • order メソッド内の testSql.getClass().getMethod("order");testSql.annotationType().getMethod("order"); へ変更しました。
  • sql メソッド内の testSql.getClass().getMethod("sql");testSql.annotationType().getMethod("sql"); へ変更しました。

InquiringStatusOfBookQueueServiceTest.java

public class InquiringStatusOfBookQueueServiceTest {

    ..........

    @Test
    public void testSendMessage() throws Exception {
        RabbitAdmin rabbitAdmin = new RabbitAdmin(connectionFactory);
        rabbitAdmin.deleteQueue(Constant.QUEUE_NAME_INQUIRING_STATUSOFBOOK);
        rabbitAdmin.declareQueue(queue);

        Long lendingAppId = Long.valueOf(1L);
        inquiringStatusOfBookQueueService.sendMessage(lendingAppId);

        InquiringStatusOfBookQueueMessage message
                = (InquiringStatusOfBookQueueMessage) rabbitTemplate.receiveAndConvert(Constant.QUEUE_NAME_INQUIRING_STATUSOFBOOK);
        assertThat(message.getLendingAppId()).isEqualTo(lendingAppId);
    }
}
  • testSendMessage メソッド内の new Long(1);Long.valueOf(1L); へ変更します。

BooklistServiceTest.java

public class BooklistServiceTest {

    ..........

    @Test
    public void testTemporarySaveBookListCsvFile() throws Exception {
        new Expectations(LendingUserDetailsHelper.class) {{
            LendingUserDetailsHelper.getLoginUserId(); result = Long.valueOf(1L);
        }};

        ..........

        Long lendingAppId = booklistService.temporarySaveBookListCsvFile(uploadBooklistForm);
        assertThat(lendingAppId).isNotEqualTo(Long.valueOf(0L));
        IDataSet dataSet = new CsvDataSet(new File("src/test/resources/ksbysample/webapp/lending/web/booklist/assertdata/001"));
  • testTemporarySaveBookListCsvFile メソッド内の以下の点を変更します。
    • new Long(1);Long.valueOf(1L); へ変更します。
    • new Long(0);Long.valueOf(0L); へ変更します。

ValuesEnumValidatorTest.java

public class ValuesEnumValidatorTest {

    // テスト用 Value 列挙型
    @SuppressWarnings("MissingOverride")
    @Getter
    @AllArgsConstructor
    private enum TestValues implements Values {
        FIRST("1", "1番目")
        , SECOND("2", "2番目")
        , THIRD("3", "3番目");

        private final String value;
        private final String text;
    }
  • TestValues 列挙型に @SuppressWarnings("MissingOverride") を付加します。

CommonWebApiResponse.java

package ksbysample.webapp.lending.webapi.common;

import lombok.Data;

@SuppressWarnings("TypeParameterUnusedInFormals")
@Data
public class CommonWebApiResponse<T> {

    private int errcode = 0;

    private String errmsg = "";

    private T content;

}
  • @SuppressWarnings("TypeParameterUnusedInFormals") を追加します。

履歴

2017/02/18
初版発行。

Spring Boot 1.3.x の Web アプリを 1.4.x へバージョンアップする ( その6 )( 「Run 'All Tests' with Coverage」実行時のエラーを解消する+build タスク実行時の警告を解消する )

概要

記事一覧はこちらです。

Spring Boot 1.3.x の Web アプリを 1.4.x へバージョンアップする ( その5 )( メールのテンプレートに使用していた Velocity を FreeMarker に変更する ) の続きです。

  • 今回の手順で確認できるのは以下の内容です。
    • 「Run ‘All Tests’ with Coverage」実行時のエラーの解消
    • 上の対応にそんなに時間がかからなかったので、build タスク実行時に出る警告も解消します

参照したサイト・書籍

  1. How to load freemarker templates from external file system folder with Spring Boot
    http://www.sandc.software/blog/how-to-load-freemarker-templates-from-external-file-system-folder-with-spring-boot/

  2. Compilation error while upgrading from Guava 19 to 20
    http://stackoverflow.com/questions/40364635/compilation-error-while-upgrading-from-guava-19-to-20

  3. google/error-prone
    https://github.com/google/error-prone

    • このページを見ると Guava 20 から依存している error_prone_annotations ライブラリは Google の静的解析ツール関連のライブラリのようです。

目次

  1. 何のエラーが出ているのか?
  2. java.lang.RuntimeException: freemarker.template.TemplateNotFoundException: Template not found for name ...
  3. 「Run ‘All Tests’ with Coverage」実行時のエラーが解消したので build タスクも試してみる
  4. 警告: タイプ'CompatibleWith'内に注釈メソッド'value()'が見つかりません: com.google.errorprone.annotations.CompatibleWithのクラス・ファイルが見つかりません
  5. 次回は。。。

手順

何のエラーが出ているのか?

失敗しているテストは 7 個、種類は以下の3種類でした。

  • Mail001HelperTest, Mail002HelperTest, Mail003HelperTest, InquiringStatusOfBookQueueListenerTest
    • java.lang.RuntimeException: freemarker.template.TemplateNotFoundException: Template not found for name "mail/mail001-body.ftl". のようにテンプレートファイルが見つからないというエラーが出ています。
  • LendingappControllerTest$貸出申請画面の正常処理時のテスト
    • java.lang.AssertionError: View name
    • Expected :lendingapp/lendingapp
    • Actual :error
  • LendingapprovalControllerTest$貸出承認画面の正常処理時のテスト
    • java.lang.AssertionError: View name
    • Expected :lendingapproval/lendingapproval
    • Actual :error

java.lang.RuntimeException: freemarker.template.TemplateNotFoundException: Template not found for name ...

上のエラーと一緒に

The name was interpreted by this TemplateLoader: MultiTemplateLoader(loader1 = FileTemplateLoader(baseDir=“C:\project-springboot\ksbysample-webapp-lending\build\resources\test\templates”, canonicalBasePath=“C:\project-springboot\ksbysample-webapp-lending\build\resources\test\templates\”), loader2 = ClassTemplateLoader(resourceLoaderClass=org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer, basePackagePath=“” / relatively to resourceLoaderClass pkg /)).

というエラーメッセージも出ています。テストクラスからテストを実行した時に src/main/resources/templates の下に配置したテンプレートファイルを参照できていないように見えます。

Web で何か情報がないかいろいろ検索するも見つからず。。。

どうしてよいのか困ってしまいましたが、設定で何とかなるように作られているはずと思い Spring Boot Reference Guide の Appendix A. Common application properties に記載されている spring.freemarker.~ の設定項目をいろいろ試してみました。

結論として以下の設定を application.properties に追加するとテストが成功するようになりました。

spring.freemarker.prefer-file-system-access=false

f:id:ksby:20170212110023p:plain

しかも他に出ていた java.lang.AssertionError: View name のエラーも消えました。上の設定を入れた後に「Run ‘All Tests’ with Coverage」を実行した結果が以下の画像です。failed のテストが1つもありません。

f:id:ksby:20170212110411p:plain

Velocity → FreeMarker への移行方法は前回の記事にまとめておきたいので、Spring Boot 1.3.x の Web アプリを 1.4.x へバージョンアップする ( その5 )( メールのテンプレートに使用していた Velocity を FreeMarker に変更する ) にもこの内容を反映しました。

デフォルト値は false にした方が良さそうな気がしますが、なぜ true なのでしょうか? 自分の実装に今まで気付いていない問題があるのかもしれませんが、現時点では分かりませんでした。。。

「Run ‘All Tests’ with Coverage」実行時のエラーが解消したので build タスクも試してみる

「Run ‘All Tests’ with Coverage」でテストが全て成功するようになり、build タスクも通るのでは?と思ったので試してみます。

無事 “BUILD SUCCESSFUL” のメッセージが表示されました! ただし guava で警告が 10 個出力されているので、その原因を調査します。

f:id:ksby:20170212112456p:plain

警告: タイプ'CompatibleWith'内に注釈メソッド'value()'が見つかりません: com.google.errorprone.annotations.CompatibleWithのクラス・ファイルが見つかりません

出力されている警告は以下のものでした。

C:\Users\root.gradle\caches\modules-2\files-2.1\com.google.guava\guava\21.0\3a3d111be1be1b745edfa7d91678a12d7ed38709\guava-21.0.jar(com/google/common/collect/Multiset.class): 警告: タイプ'CompatibleWith'内に注釈メソッド'value()‘が見つかりません: com.google.errorprone.annotations.CompatibleWithのクラス・ファイルが見つかりません

調べると stackoverflow の QA で Compilation error while upgrading from Guava 19 to 20 が見つかりました。Guava の 20 から error_prone_annotations ライブラリに依存していると書かれているので、build.gradle に追加してみます。

build.gradle を リンク先の内容 に変更します。変更後、Gradle Tool Window の左上にある「Refresh all Gradle projects」ボタンをクリックして更新します。

clean タスク → Rebuild Project → build タスク の順に実行すると今度は警告が出ませんでした。

f:id:ksby:20170212134741p:plain

次回は。。。

今回初めて知った GoogleJava 静的解析ツール Error Prone に興味が湧いたので試してみたいと思います。

ソースコード

build.gradle

dependencies {
    ..........

    // dependency-management-plugin によりバージョン番号が自動で設定されないもの、あるいは最新バージョンを指定したいもの
    runtime("${jdbcDriver}")
    compile("org.seasar.doma:doma:2.15.0")
    compile("org.bgee.log4jdbc-log4j2:log4jdbc-log4j2-jdbc4.1:1.16")
    compile("org.apache.commons:commons-lang3:3.5")
    compile("com.google.guava:guava:21.0")
    compileOnly("com.google.errorprone:error_prone_annotations:2.0.15")
    compile("org.simpleframework:simple-xml:2.7.1")
  • compileOnly("com.google.errorprone:error_prone_annotations:2.0.15") を追加します。

履歴

2017/02/12
初版発行。

Spring Boot 1.3.x の Web アプリを 1.4.x へバージョンアップする ( その5 )( メールのテンプレートに使用していた Velocity を FreeMarker に変更する )

概要

記事一覧はこちらです。

Spring Boot 1.3.x の Web アプリを 1.4.x へバージョンアップする ( その4 )( build.gradle 修正後の Rebuild で出た Warning を解消する ) の続きです。

  • 今回の手順で確認できるのは以下の内容です。
    • java: org.springframework.ui.velocityのorg.springframework.ui.velocity.VelocityEngineUtilsは非推奨になりました の対応として、これまでメールのテンプレートに使用していた Velocity を FreeMarker に変更します。
    • 今後のサンプル作成で Thymeleaf 3 は必ずさわると思うので、今回は FreeMarker を使用する場合の対応方法を調べたいと思います。

参照したサイト・書籍

  1. FreeMarker Java Template Engine
    http://freemarker.org/

  2. Spring BootアプリのテストをSpockで書く
    http://int128.hatenablog.com/entry/2016/12/13/003600

  3. Spring Boot Security + Thymeleaf : IProcessorDialect class missing
    http://stackoverflow.com/questions/37270322/spring-boot-security-thymeleaf-iprocessordialect-class-missing

  4. How to check if a variable exists in a FreeMarker template?
    http://stackoverflow.com/questions/306732/how-to-check-if-a-variable-exists-in-a-freemarker-template

  5. The Move from Velocity to FreeMarker with Spring Boot
    http://nixmash.com/java/the-move-from-velocity-to-freemarker-with-spring-boot/

目次

  1. build.gradle を変更する
  2. spring-boot-starter-freemarker では何が auto-configuration されるのか?
  3. application.properties を変更する
  4. VelocityUtils → FreeMarkerUtils へ変更する
  5. FreeMarkerUtilsTest クラスを作成して動作確認する
  6. メールのテンプレートファイルを変更する
  7. ksbysample.webapp.lending.helper.mail パッケージの下の MailxxxHelper クラスを変更する
  8. 動作確認はまだできないので Rebuild Project の確認だけ行う
  9. 次回は。。。

手順

build.gradle を変更する

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

  2. Gradle Tool Window の左上にある「Refresh all Gradle projects」ボタンをクリックして更新します。

    FreeMaker のホームページ を見ると最新版は Latest stable release: 2.3.25-incubating と書かれていますが、spring-boot-starter-freemarker を追加してダウンロードされたバージョンも同じでした。

    f:id:ksby:20170211102648p:plain

spring-boot-starter-freemarker では何が auto-configuration されるのか?

Spring Boot でメール送信する Web アプリケーションを作る ( その6 )( メール送信画面の作成 ) で spring-boot-starter-velocity を調べた時のように spring-boot-starter-freemarker の AutoConfiguration の動作を確認します。

org.springframework.boot.autoconfigure.freemarker の FreeMarkerAutoConfiguration クラスが FreeMarker の AutoConfiguration クラスで、ソースを見て分かることは、

  • spring.freemarker.enabled = false を設定すれば、HTML のテンプレートファイル用の設定は反映されません ( freeMarkerViewResolver Bean が生成されません )。
  • VelocityEngine に該当するのは freemarker.template.Configuration を返す freeMarkerConfiguration Bean のようです。FreeMarker の Manual の Create a configuration instanceGet the template を見た感じでは freemarker.template.Configuration から freemarker.template.Template を生成すればメールのテンプレートとして利用できそうです。
  • テンプレートファイルの拡張子は .ftl です ( これは org.springframework.boot.autoconfigure.freemarker の下の FreeMarkerProperties クラスに記述があります )。

また application.properties に設定する項目を Spring Boot Reference Guide の Appendix A. Common application properties で確認すると、

  • FreeMarker の設定は spring.freemarker.~ で設定します。
  • キャッシュの設定がデフォルトでは spring.freemarker.cache=false と書かれています。デフォルトは有効で DevTools を入れると無効になる、という訳ではないようです。IntelliJ IDEA の補完ではデフォルト値は表示されないのですが、どちらが正しいのかは build まで通った後に忘れていなければ検証したいと思います。

    f:id:ksby:20170211111354p:plain

    ちなみに DevTools を入れると FreeMarker のキャッシュが無効になることは org.springframework.boot.devtools.env の下の DevToolsPropertyDefaultsPostProcessor クラスに properties.put("spring.freemarker.cache", "false"); と記述されていることで確認しています。

application.properties を変更する

  1. src/main/resources の下の application.properties を リンク先の内容 に変更します。

VelocityUtils → FreeMarkerUtils へ変更する

  1. Project Tool Window で ksbysample.webapp.lending.util.velocity を選択し、Shift+F6 を押して「Rename」ダイアログを表示した後、"velocity" → “freemarker” へ変更して「OK」ボタンをクリックします。

    f:id:ksby:20170211115102p:plain

  2. Project Tool Window で ksbysample.webapp.lending.util.freemarker の下の VelocityUtils.java を選択し、Shift+F6 を押して「Rename」ダイアログを表示した後、"VelocityUtils" → “FreeMarkerUtils” へ変更して「OK」ボタンをクリックします。

    f:id:ksby:20170211115443p:plain

    今回は使用先のフィールドの変数名も変更するかを確認する「Rename Variables」ダイアログが表示されますので「Select all」ボタンを押して選択した後「OK」ボタンをクリックします。

    f:id:ksby:20170211181819p:plain

  3. src/main/java/ksbysample/webapp/lending/util/freemarker の下の FreeMarkerUtils.javaリンク先の内容 に変更します。

FreeMarkerUtilsTest クラスを作成して動作確認する

テストは Spock で作ります。Spring IO Platform で指定される Spock のバージョン番号は 1.0-groovy-2.4 なのですが、このバージョンではまだ Spring Boot の 1.4 から導入されたテスト用の新アノテーションに対応していないとのことなので、対応されている 1.1 をバージョン番号を指定して導入します。

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

  2. Gradle Tool Window の左上にある「Refresh all Gradle projects」ボタンをクリックして更新します。

次にテストクラスを作ります。

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

    f:id:ksby:20170211125157p:plain

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

  2. src/test/groovy/ksbysample/webapp/lending/util/freemarker の下に FreeMarkerUtilsTest.groovy が作成されますので、リンク先のその1の内容 に変更します。

  3. src/test/resources の下に templates/mail ディレクトリを作成します。

  4. src/test/resources/templates/mail の下に FreeMarkerUtilsTest-001.ftl を作成し、リンク先の内容 を記述します。

  5. テストを実行してみます。が、以下のソースで java: パッケージorg.apache.commons.langは存在しません というエラーメッセージが表示されました。

    • src/main/java/ksbysample/webapp/lending/web/lendingapp/LendingappController.java
    • src/main/java/ksbysample/webapp/lending/web/ExceptionHandlerAdvice.java

    どうも 1.0 の spock の依存関係に org.apache.commons.lang.StringUtils が入っていて、意識せずにそちらを使用していたようです。import org.apache.commons.lang.StringUtils;import org.apache.commons.lang3.StringUtils; へ変更します。

  6. 再度テストを実行してみます。が、今度は “java.lang.NoClassDefFoundError: org/thymeleaf/dialect/IExpressionObjectDialect” のエラーが出ました。このエラーを解消しないとテストが通らないようです。原因を調べます。

    f:id:ksby:20170211183545p:plain

  7. Web で検索したところ、stackoverflow で Spring Boot Security + Thymeleaf : IProcessorDialect class missing という QA を見つけました。

    build.gradle を見ると thymeleaf-extras-springsecurity4 は Spring IO Platform によりバージョン番号が自動で設定されるようにしていましたが、compile("org.thymeleaf.extras:thymeleaf-extras-java8time:3.0.0.RELEASE") という指定を別にしていました。これが原因ですね。

    Spring IO Platform の Appendix A. Dependency versions を見ると org.thymeleaf.extras:thymeleaf-extras-java8time が記述されていて Spring IO Platform の対象にできることが判明したので、バージョン番号を build.gradle で指定しないようにします。

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

  9. Gradle Tool Window の左上にある「Refresh all Gradle projects」ボタンをクリックして更新します。

  10. 再度テストを実行すると今度は成功しました。

    f:id:ksby:20170211191343p:plain

    でもなんかテストに時間がかかるような。。。 最初、このテストに Web 環境は不要なので @SpringBootTest(webEnvironment = MOCK) ではなく @SpringBootTest(webEnvironment = NONE) を指定したのですが、この記述だと ksbysample.common.test.rule.mockmvc の SecurityMockMvcResource クラスで @Autowired private WebApplicationContext context; に DI が出来なくてエラーになったので、MOCK に変更しました。

    SecurityMockMvcResource クラスの Bean 生成を Web 環境の時だけに出来ないかな?と思って @Conditional 系アノテーションを調べたところ @ConditionalOnWebApplication というのがあったので、SecurityMockMvcResource クラスに付加したいと思います。

  11. src/test/java/ksbysample/common/test/rule/mockmvc の下の SecurityMockMvcResource.javaリンク先の内容 に変更します。

  12. テストを実行すると成功し、実行時間も4~5秒程度速くなりました。実施したことの割に速くなり過ぎでは?とも思いましたが、今は気にしないことにします。

    f:id:ksby:20170211193737p:plain

  13. もう少しテストを追加します。src/test/groovy/ksbysample/webapp/lending/util/freemarker の下の FreeMarkerUtilsTest.groovy を リンク先のその3の内容 に変更します。

  14. src/test/resources/templates/mail の下に以下のファイルを作成し、リンク先の内容 を記述します。

    • FreeMarkerUtilsTest-002.ftl
    • FreeMarkerUtilsTest-002-result.txt
    • FreeMarkerUtilsTest-003.ftl
    • FreeMarkerUtilsTest-003-result.txt
    • FreeMarkerUtilsTest-003-result2.txt
  15. テストを実行して全て成功することを確認します。

f:id:ksby:20170212010447p:plain

メールのテンプレートファイルを変更する

  1. src/main/resources/templates/mail の下のファイルの拡張子を全て .vm.ftl に変更します。

  2. src/main/resources/templates/mail の下の mail003-body.ftlリンク先の内容 に変更します。

ksbysample.webapp.lending.helper.mail パッケージの下の MailxxxHelper クラスを変更する

必要があるかなと思っていたのですが、IntelliJ IDEA のリファクタリングの機能でいろいろ変更をした時に MailxxxHelper クラスにも必要な変更が反映されていて、この時点では何もすることがありませんでした。

以下のソースに自動で変更が反映されています。

  • src/main/java/ksbysample/webapp/lending/helper/mail/Mail001Helper.java
  • src/main/java/ksbysample/webapp/lending/helper/mail/Mail002Helper.java
  • src/main/java/ksbysample/webapp/lending/helper/mail/Mail003Helper.java

変更内容は以下の点です。

  • private VelocityUtils velocityUtils;private FreeMarkerUtils freeMarkerUtils; へ変更します。
  • TEMPLATE_LOCATION_TEXTMAIL 定数で指定しているテンプレートファイルの拡張子を .vm.ftl へ変更します。

動作確認はまだできないので Rebuild Project の確認だけ行う

動作確認は build でエラーが出なくなってから行いますので、この時点では clean タスク実行 → Rebuild Project を実行して Warning が1つも出ないことだけ確認します。

f:id:ksby:20170212014837p:plain

次回は。。。

「Run ‘All Tests’ with Coverage」と build タスクを実行してみましたが、まだエラーが出ていますので次回は「Run ‘All Tests’ with Coverage」実行時のエラーを解消します。

f:id:ksby:20170212024752p:plain f:id:ksby:20170212024239p:plain

また今回 Velocity → FreeMarker へ切り替えてみて、以下の感想でした。

  • 使い勝手はほとんど変わらず、むしろ FreeMarker の方が高機能です。
  • FreeMarker は Web 上のマニュアルも綺麗でまとまっていて分かりやすい!

2010 年で開発が止まっている Velocity に対して Spring Boot がサポートを終了するのも仕方がないかな、と思いました。

と、ここまで書いてから The Move from Velocity to FreeMarker with Spring Boot の記事を見つけました。org.springframework.ui.freemarker の下に FreeMarkerTemplateUtils クラスなんてあるんですね。。。

ソースコード

build.gradle

■その1

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

    // 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-web")
    compile("org.springframework.boot:spring-boot-starter-thymeleaf")
    compile("org.thymeleaf.extras:thymeleaf-extras-springsecurity4")
    compile("org.springframework.boot:spring-boot-starter-data-jpa")
    compile("org.springframework.boot:spring-boot-starter-freemarker")
    compile("org.springframework.boot:spring-boot-starter-mail")
    compile("org.springframework.boot:spring-boot-starter-security")
    ..........
  • compile("org.springframework.boot:spring-boot-starter-velocity")compile("org.springframework.boot:spring-boot-starter-freemarker") へ変更します。

■その2

dependencies {
    def jdbcDriver = "org.postgresql:postgresql:9.4.1212"
    def spockVersion = "1.1-groovy-2.4-rc-3"

    // dependency-management-plugin によりバージョン番号が自動で設定されるもの
    // Appendix A. Dependency versions ( http://docs.spring.io/platform/docs/current/reference/htmlsingle/#appendix-dependency-versions ) 参照
    ..........

    // dependency-management-plugin によりバージョン番号が自動で設定されないもの、あるいは最新バージョンを指定したいもの
    ..........
    testCompile("org.jmockit:jmockit:1.30")
    testCompile("org.spockframework:spock-core:${spockVersion}") {
        exclude module: "groovy-all"
    }
    testCompile("org.spockframework:spock-spring:${spockVersion}") {
        exclude module: "groovy-all"
    }
  • def spockVersion = "1.1-groovy-2.4-rc-3" を追加します。
  • org.spockframework:spock-coreorg.spockframework:spock-spring をバージョン番号を指定するので記述位置を下へ変更します。
  • testCompile("org.spockframework:spock-core")testCompile("org.spockframework:spock-core:${spockVersion}") へ変更します。
  • testCompile("org.spockframework:spock-spring")testCompile("org.spockframework:spock-spring:${spockVersion}") へ変更します。

■その3

dependencies {
    def jdbcDriver = "org.postgresql:postgresql:9.4.1212"
    def spockVersion = "1.1-groovy-2.4-rc-3"

    // 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-web")
    compile("org.springframework.boot:spring-boot-starter-thymeleaf")
    compile("org.thymeleaf.extras:thymeleaf-extras-springsecurity4")
    compile("org.thymeleaf.extras:thymeleaf-extras-java8time")
    compile("org.springframework.boot:spring-boot-starter-data-jpa")
    ..........
  • compile("org.thymeleaf.extras:thymeleaf-extras-java8time:3.0.0.RELEASE")compile("org.thymeleaf.extras:thymeleaf-extras-java8time") へ変更し、記述位置を上へ変更します。

application.properties

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

spring.jpa.hibernate.ddl-auto=none
spring.jpa.hibernate.naming_strategy=org.hibernate.cfg.ImprovedNamingStrategy

spring.freemarker.cache=true
spring.freemarker.charset=UTF-8
spring.freemarker.enabled=false
spring.freemarker.prefer-file-system-access=false
  • 以下の設定を削除します。
    • spring.velocity.enabled=false
    • spring.velocity.charset=UTF-8
  • 以下の設定を追加します。
    • spring.freemarker.cache=true
    • spring.freemarker.charset=UTF-8
    • spring.freemarker.enabled=false
    • spring.freemarker.prefer-file-system-access=false
      • デフォルトは true ですが、この設定を入れないとテストクラスからテストを実行した時に src/main/resources の下のテンプレートファイルを見に行ってくれませんでした。なぜデフォルト値が true なのか疑問です。。。
  • spring.jpa.hibernate.naming_strategy の設定は非推奨か無くなっているようなので ( IntelliJ IDEA のエディタ上で取消線が表示されます )、次回以降に見直します。

FreeMarkerUtils.java

package ksbysample.webapp.lending.util.freemarker;

import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.io.StringWriter;
import java.util.Map;

@Component
public class FreeMarkerUtils {

    private final Configuration freeMarkerConfiguration;

    public FreeMarkerUtils(Configuration freeMarkerConfiguration) {
        this.freeMarkerConfiguration = freeMarkerConfiguration;
    }

    public String merge(String templateLocation, Map<String, Object> model) {
        Template template = getTemplate(templateLocation);
        return process(template, model);
    }

    private Template getTemplate(String templateLocation) {
        try {
            return this.freeMarkerConfiguration.getTemplate(templateLocation);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    private String process(Template template, Map<String, Object> model) {
        try {
            StringWriter sw = new StringWriter();
            template.process(model, sw);
            return sw.toString();
        } catch (TemplateException | IOException e) {
            throw new RuntimeException(e);
        }
    }

}

FreeMarkerUtilsTest.groovy

■その1

package ksbysample.webapp.lending.util.freemarker

import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import spock.lang.Specification

import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.MOCK

@SpringBootTest(webEnvironment = MOCK)
class FreeMarkerUtilsTest extends Specification {

    @Autowired
    FreeMarkerUtils freeMarkerUtils

    def "テンプレートファイルから文字列を生成する_変数のみの場合"() {
        setup:
        def model = [username: "田中 太郎"]

        expect:
        freeMarkerUtils.merge("mail/FreeMarkerUtilsTest-001.ftl", model) == "田中 太郎"
    }

}

■その2

import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.NONE

@SpringBootTest(webEnvironment = NONE)
class FreeMarkerUtilsTest extends Specification {
  • @SpringBootTest(webEnvironment = MOCK)@SpringBootTest(webEnvironment = NONE) に変更します。

■その3

package ksbysample.webapp.lending.util.freemarker

import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import spock.lang.Specification

import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.NONE

@SpringBootTest(webEnvironment = NONE)
class FreeMarkerUtilsTest extends Specification {

    @Autowired
    FreeMarkerUtils freeMarkerUtils

    class TestUser {
        String name
        Integer age
        String address
    }

    def "テンプレートファイルから文字列を生成する_変数のみの場合"() {
        setup:
        def model = [username: "田中 太郎"]

        expect:
        freeMarkerUtils.merge("mail/FreeMarkerUtilsTest-001.ftl", model) == "田中 太郎"
    }

    /**
     * 変数が null の場合にエラーにならないようにするには、テンプレートファイルの変数の最後に ! を付けること。
     * ${xxx} ではなく ${xxx!} のように書く。
     * null の時にはスペースで埋めたい時には ${xxx!?left_pad(3)} のように書ける
     * 
     * ただし、この記述にすると model に必要なデータがない時に例外が throw されないので注意すること
     */
    def "テンプレートファイルから文字列を生成する_変数がnullの場合"() {
        setup:
        def model = [username: null]

        expect:
        freeMarkerUtils.merge("mail/FreeMarkerUtilsTest-001.ftl", model) == ""
    }

    def "テンプレートファイルから文字列を生成する_変数+クラスの場合"() {
        setup:
        def model = [
                username: "田中 太郎"
                , user  : new TestUser(age: 25, address: "東京都千代田区")
        ]
        def result = new File("src/test/resources/templates/mail/FreeMarkerUtilsTest-002-result.txt").text

        expect:
        freeMarkerUtils.merge("mail/FreeMarkerUtilsTest-002.ftl", model) == result
    }

    def "テンプレートファイルから文字列を生成する_リストの場合"() {
        setup:
        def userList = [
                new TestUser(name: "田中 太郎", age: 25, address: "東京都千代田区")
                , new TestUser(name: "鈴木 花子", age: 8, address: "神奈川県横浜市")
                , new TestUser(name: "高橋 孝", age: 100, address: "埼玉県大宮市")
        ]
        def model = [userList: userList]
        def result = new File("src/test/resources/templates/mail/FreeMarkerUtilsTest-003-result.txt").text

        expect:
        freeMarkerUtils.merge("mail/FreeMarkerUtilsTest-003.ftl", model) == result
    }

    def "テンプレートファイルから文字列を生成する_リストでnameの一部がnullの場合"() {
        setup:
        def userList = [
                new TestUser(name: "田中 太郎", age: 25, address: "東京都千代田区")
                , new TestUser(name: null, age: 8, address: "神奈川県横浜市")
                , new TestUser(name: "高橋 孝", age: 100, address: "埼玉県大宮市")
        ]
        def model = [userList: userList]
        def result = new File("src/test/resources/templates/mail/FreeMarkerUtilsTest-003-result2.txt").text

        expect:
        freeMarkerUtils.merge("mail/FreeMarkerUtilsTest-003.ftl", model) == result
    }

    def "テンプレートファイルが存在しない場合はエラーになる"() {
        given:
        def model = [username: "田中 太郎"]

        when:
        freeMarkerUtils.merge("mail/FreeMarkerUtilsTest-notFound.ftl", model)

        then:
        RuntimeException e = thrown()
        e.getMessage() contains "Template not found"
    }

}

FreeMarkerUtilsTest-001.ftl

${username!}
  • 変数名の最後に ! を付けておくと、値が null の時にエラーになりません。

SecurityMockMvcResource.java

@Component
@ConditionalOnWebApplication
public class SecurityMockMvcResource extends ExternalResource {
  • @ConditionalOnWebApplication を追加します。

FreeMarkerUtilsTest-002.ftl, FreeMarkerUtilsTest-002-result.txt, FreeMarkerUtilsTest-003.ftl, FreeMarkerUtilsTest-003-result.txt, FreeMarkerUtilsTest-003-result2.txt

■FreeMarkerUtilsTest-002.ftl

氏名: ${username!}
年齢: ${user.age!}
住所: ${user.address!}

■FreeMarkerUtilsTest-002-result.txt

氏名: 田中 太郎
年齢: 25
住所: 東京都千代田区

■FreeMarkerUtilsTest-003.ftl

氏名             年齢  住所
----------------------------------------------------------
<#list userList as user>
${user.name!?right_pad(8, " ")}  ${user.age!?left_pad(3)}  ${user.address!}
</#list>

■FreeMarkerUtilsTest-003-result.txt

氏名             年齢  住所
----------------------------------------------------------
田中 太郎      25  東京都千代田区
鈴木 花子       8  神奈川県横浜市
高橋 孝      100  埼玉県大宮市

■FreeMarkerUtilsTest-003-result2.txt

氏名             年齢  住所
----------------------------------------------------------
田中 太郎      25  東京都千代田区
            8  神奈川県横浜市
高橋 孝      100  埼玉県大宮市

mail003-body.ftl

貸出申請が承認・却下されました。
========================================================================
承認/却下 書籍
------------------------------------------------------------------------
<#list mail003BookDataList as bookData>
 ${bookData.approvalResultStr}   ${bookData.bookName}
</#list>
========================================================================

詳細は以下のURLから確認してください。

http://localhost:8080/confirmresult?lendingAppId=${lendingAppId}

履歴

2017/02/12
初版発行。

Spring Boot 1.3.x の Web アプリを 1.4.x へバージョンアップする ( その4 )( build.gradle 修正後の Rebuild で出た Warning を解消する )

概要

記事一覧はこちらです。

Spring Boot 1.3.x の Web アプリを 1.4.x へバージョンアップする ( その3 )( build.gradle の修正 ) の続きです。

  • 今回の手順で確認できるのは以下の内容です。
    • build.gradle 修正後の Rebuild Project 実行時に出た Warning の解消
    • Velocity は Thymeleaf 3 か FreeMarker に変更しようと思っています。他の Warning を解消してから次回以降に変更します。今回は何もしません。

参照したサイト・書籍

目次

  1. java: com.univocity.parsers.common.CommonParserSettingsのsetRowProcessor(com.univocity.parsers.common.processor.RowProcessor)は非推奨になりました
  2. java: org.springframework.boot.testのorg.springframework.boot.test.SpringApplicationConfigurationは非推奨になりました
  3. 次回は。。。

手順

java: com.univocity.parsers.common.CommonParserSettingsのsetRowProcessor(com.univocity.parsers.common.processor.RowProcessor)は非推奨になりました

uniVocity-parsers の JavaDoc で CsvParserSettings#setRowProcessor の説明を見ると “Use the setProcessor(Processor) method …” と記述されていました。

Tutorials はまだ CsvParserSettings#setRowProcessor が使用されたままでしたが、GitHub のテストコードを見ると univocity-parsers/src/test/java/com/univocity/parsers/common/processor/AnnotatedBeanProcessorTest.java で CsvParserSettings#setProcessor が使用されていました。CsvParserSettings#setRowProcessor → CsvParserSettings#setProcessor に変更すればよいだけのようです。

以下のソース内の .setRowProcessor.setProcessor に変更します。

  • src/main/java/ksbysample/webapp/lending/service/file/BooklistCsvFileService.java

java: org.springframework.boot.testのorg.springframework.boot.test.SpringApplicationConfigurationは非推奨になりました

Spring Boot 1.4 Release Notes を見ると “From @SpringApplicationConfiguration(classes=MyConfig.class) to @SpringBootTest(classes=MyConfig.class)” と記述されていますので、テストコード内の @SpringApplicationConfiguration@SpringBootTest へ変更します。

Ctrl+Shift+R を押して「Replace in Path」ダイアログを表示して置換します。

f:id:ksby:20170211084737p:plain

以下のダイアログが表示されたら「All Files」ボタンをクリックします。

f:id:ksby:20170211085717p:plain

これだけでは import 文が修正されませんので、「Replace in Path」ダイアログで import org.springframework.boot.test.SpringApplicationConfiguration;import org.springframework.boot.test.context.SpringBootTest; へ変更します。

f:id:ksby:20170211085933p:plain

最後に Project Tool Window で src/test を選択した後、Ctrl+Alt+O を押して「Optimize Imports」ダイアログを表示して「Run」ボタンを押して import 文を最適化します。最適化した結果を見て気づきましたが、不要な import 文を消していないソースが結構ありました。。。

clean タスク実行 → Rebuild Project を実行して Velocity 以外の Warning が消えていることを確認します。

f:id:ksby:20170211091141p:plain

次回は。。。

java: org.springframework.ui.velocityのorg.springframework.ui.velocity.VelocityEngineUtilsは非推奨になりました の対応として、Velocity を Thymeleaf 3 か FreeMarker に変更する予定です。

履歴

2017/02/11
初版発行。  

Spring Boot 1.3.x の Web アプリを 1.4.x へバージョンアップする ( その3 )( build.gradle の修正 )

概要

記事一覧はこちらです。

Spring Boot 1.3.x の Web アプリを 1.4.x へバージョンアップする ( その2 )( IntelliJ IDEA の Gradle Tool Window の「Refresh all Gradle projects」を押してもエラーが出ないようにする ) の続きです。

  • 今回の手順で確認できるのは以下の内容です。
    • build.gradle の修正

参照したサイト・書籍

  1. gradle 2.12 で対応された compileOnly を試す
    http://qiita.com/or_die/items/709b5c37ff34ee2839b3

  2. gradleのcompileOnlyで指定したライブラリはtestCompileに引き継がれない
    http://qiita.com/gillax/items/1cbcff003384087f2db1

  3. 3.Maven 入門 (2)
    http://www.techscore.com/tech/Java/ApacheJakarta/Maven/3/

    • maven の provided scope を調べた時に参照しました。

目次

  1. Spring Initializr で 1.4.4 のプロジェクトを作成する
  2. build.gradle を修正して build してみる
  3. 次回は。。。

手順

Spring Initializr で 1.4.4 のプロジェクトを作成する

  1. 「Welcome to IntelliJ IDEA」ダイアログで「Create New Project」をクリックします。

    f:id:ksby:20170210002217p:plain

  2. 「New Project」ダイアログが表示されます。画面左側のリストから「Spring Initializr」を選択した後、「Next」ボタンをクリックします。

    f:id:ksby:20170210002435p:plain

  3. 次の画面が表示されます。「Type」で「Gradle Project」を選択した後、「Next」ボタンをクリックします。

    f:id:ksby:20170210002709p:plain

  4. 次の画面が表示されます。画面中央上の「Spring Boot」で「1.4.4」を選択してから ksbysample-webapp-lending プロジェクトで使用している以下の項目をチェックした後、「Next」ボタンをクリックします。

    f:id:ksby:20170210003303p:plain f:id:ksby:20170210003427p:plain f:id:ksby:20170210003552p:plain f:id:ksby:20170210003727p:plain f:id:ksby:20170210003928p:plain

  5. 次の画面が表示されます。「Project location」を “C:\project-springboot\demo” に変更した後、「Finish」ボタンをクリックします。

    f:id:ksby:20170210004339p:plain

  6. 「Import Module from Gradle」ダイアログが表示されます。「Create directories for empty content roots automatically」をチェックした後、「OK」ボタンをクリックします。

    f:id:ksby:20170210004636p:plain

これでプロジェクトが作成されて以下の build.gradle が作成されました。

buildscript {
    ext {
        springBootVersion = '1.4.4.RELEASE'
    }
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
    }
}

apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'org.springframework.boot'

jar {
    baseName = 'demo'
    version = '0.0.1-SNAPSHOT'
}

sourceCompatibility = 1.8

repositories {
    mavenCentral()
}


dependencies {
    compile('org.springframework.boot:spring-boot-starter-data-redis')
    compile('org.springframework.boot:spring-boot-starter-mail')
    compile('org.springframework.boot:spring-boot-starter-security')
    compile('org.springframework.session:spring-session')
    compile('org.springframework.boot:spring-boot-starter-thymeleaf')
    compile('org.springframework.boot:spring-boot-starter-web')
    runtime('org.springframework.boot:spring-boot-devtools')
    compileOnly('org.projectlombok:lombok')
    testCompile('org.springframework.boot:spring-boot-starter-test')
}

以下の点が 1.3.5 で生成した時とは違っていました。これらは反映したいと思います。

  • 以前は apply plugin: 'spring-boot' でしたが apply plugin: 'org.springframework.boot' に変わっています。
  • 以前は入っていた eclipse { ... } の記述がなくなっています。
  • lombok は compile(...) ではなく compileOnly(...) になっています。maven の provided scope に対応したものだそうです。

build.gradle を修正して build してみる

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

  2. Gradle Tool Window の左上にある「Refresh all Gradle projects」ボタンをクリックして更新します。

    Gradle DSL method not found: 'compileOnly()' のエラーが表示されました。

    f:id:ksby:20170210022345p:plain

  3. 先程 Spring Initializr で作成したプロジェクトと ksbysample-webapp-lending プロジェクトの gradle のバージョンを gradle/wrapper/gradle-wrapper.properties を見て比較すると、前者が gradle-2.13-bin.zip、後者が gradle-2.2-bin.zip でした。

    compileOnly が追加されたのが 2.12 からなので、gradle のバージョンが低いことが原因のようです。

  4. gradle をバージョンアップします。最初に build.gradle を リンク先のその2の内容 に変更します。

  5. gradlew wrapper コマンドを実行します。が、Spring Boot plugin's support for Gradle 2.2 is deprecated. Please upgrade to Gradle 2.9 or later. と出力されてエラーになりました。

    f:id:ksby:20170210024110p:plain

    これは一旦 build.gradle を元に戻して gradle を先にバージョンアップした方がよさそうですね。。。

  6. 現在の build.gradle の内容を別のテキストファイルに退避し、Git でソースの変更を破棄します。

  7. 変更前の build.gradle に対して リンク先のその2の内容 の変更を反映した後、gradlew wrapper コマンドを実行します。

    今度は成功しました。gradle/wrapper/gradle-wrapper.properties を見ると gradle-2.13-bin.zip になっていました。

    f:id:ksby:20170210030459p:plain

  8. 再び build.gradle を リンク先のその1の内容 に変更します。リンク先のその2の内容 も反映します。

  9. Gradle Tool Window の左上にある「Refresh all Gradle projects」ボタンをクリックして更新します。今度はエラー等は出ずに終了しました。

  10. clean タスク実行 → Rebuild Project 実行 を実行します。

    Error が 0個、Warning が 37 個出ました。Warning は以下の3種類でした。

    • java: org.springframework.ui.velocityのorg.springframework.ui.velocity.VelocityEngineUtilsは非推奨になりました
    • java: com.univocity.parsers.common.CommonParserSettingsのsetRowProcessor(com.univocity.parsers.common.processor.RowProcessor)は非推奨になりました
    • java: org.springframework.boot.testのorg.springframework.boot.test.SpringApplicationConfigurationは非推奨になりました

    Velocity は Spring Boot 1.5 からサポートされなくなると聞いていましたが、1.4 から非推奨ですか。。。 あとは uniVocity-parsers で RowProcessor が非推奨になったのと、1.4 からテスト用のアノテーションが大きく変更されているようなのでその影響による Warning ですね。

  11. build タスクを実行します。

    テストの結果は 178 tests completed, 165 failed, 8 skipped で、BUILD FAILED が表示されました。Caused by: java.lang.ClassNotFoundException が大量に出力されているのが気になります。

    f:id:ksby:20170211010128p:plain

  12. Project Tool Window の src/test から「Run ‘All Tests’ with Coverage」も実行してみます。

    build タスクの結果から分かっていましたが、ほぼ全滅でした。。。 また build タスクで大量に出ていた ClassNotFoundException ですが、java.lang.NoClassDefFoundError: org/thymeleaf/dialect/IExpressionObjectDialect が原因のようです。

    f:id:ksby:20170211010819p:plain

次回は。。。

1.2.x → 1.3.x へバージョンアップした時と同様に、Rebuild Project 実行時の Warning の解消 → Run ‘All Tests’ with Coverage のエラーの解消 → build タスクの再実行 ( エラーが出れば解消します ) の順で進める予定です。

Velocity の非推奨の対応が悩みどころです。Thymeleaf の 3 から TEXT モードがあるので、これでしょうか。Web で検索したら migration guide がヒットしたのでちょっと読んでみたいと思います。Velocity の対応は後回しにして他の部分だけ進めるかもしれません。

ソースコード

build.gradle

■その1

buildscript {
    ext {
        springBootVersion = '1.4.4.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.1.RELEASE")
        // for Grgit
        classpath("org.ajoberstar:grgit:1.8.0")
        // Gradle Download Task
        classpath("de.undercouch:gradle-download-task:3.2.0")
    }
}

apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'idea'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'
apply plugin: 'de.undercouch.download'
apply plugin: 'groovy'

sourceCompatibility = 1.8
targetCompatibility = 1.8

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

// for Doma 2
// JavaクラスとSQLファイルの出力先ディレクトリを同じにする
processResources.destinationDir = compileJava.destinationDir
// コンパイルより前にSQLファイルを出力先ディレクトリにコピーするために依存関係を逆転する
compileJava.dependsOn processResources

jar {
    baseName = 'ksbysample-webapp-lending'
    version = '1.1.0-RELEASE'
}

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

configurations {
    domaGenRuntime
}

repositories {
    jcenter()
}

dependencyManagement {
    imports {
        mavenBom 'io.spring.platform:platform-bom:Athens-SR3'
    }
}

bootRepackage {
    mainClass = 'ksbysample.webapp.lending.Application'
    excludeDevtools = true
}

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

    // 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-web")
    compile("org.springframework.boot:spring-boot-starter-thymeleaf")
    compile("org.thymeleaf.extras:thymeleaf-extras-springsecurity4")
    compile("org.springframework.boot:spring-boot-starter-data-jpa")
    compile("org.springframework.boot:spring-boot-starter-velocity")
    compile("org.springframework.boot:spring-boot-starter-mail")
    compile("org.springframework.boot:spring-boot-starter-security")
    compile("org.springframework.boot:spring-boot-starter-redis")
    compile("org.springframework.boot:spring-boot-starter-amqp")
    compile("org.springframework.boot:spring-boot-devtools")
    compile("org.springframework.session:spring-session")
    compile("org.codehaus.janino:janino")
    compile("com.fasterxml.jackson.datatype:jackson-datatype-jsr310")
    compile("com.fasterxml.jackson.dataformat:jackson-dataformat-xml")
    testCompile("org.springframework.boot:spring-boot-starter-test")
    testCompile("org.springframework.security:spring-security-test")
    testCompile("org.yaml:snakeyaml")
    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.15.0")
    compile("org.bgee.log4jdbc-log4j2:log4jdbc-log4j2-jdbc4.1:1.16")
    compile("org.apache.commons:commons-lang3:3.5")
    compile("com.google.guava:guava:21.0")
    compile("org.simpleframework:simple-xml:2.7.1")
    compile("com.univocity:univocity-parsers:2.3.1")
    compile("org.thymeleaf.extras:thymeleaf-extras-java8time:3.0.0.RELEASE")
    testCompile("org.dbunit:dbunit:2.5.3")
    testCompile("com.icegreen:greenmail:1.5.3")
    testCompile("org.assertj:assertj-core:3.6.2")
    testCompile("com.jayway.jsonpath:json-path:2.2.0")
    testCompile("org.jmockit:jmockit:1.30")

    // for lombok
    compileOnly("org.projectlombok:lombok:1.16.12")
    testCompileOnly("org.projectlombok:lombok:1.16.12")

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

bootRun {
    jvmArgs = ['-Dspring.profiles.active=develop']
}

test {
    jvmArgs = ['-Dspring.profiles.active=unittest']
}

// for Doma-Gen
task domaGen << {
    // まず変更が必要なもの
    def rootPackageName  = 'ksbysample.webapp.lending'
    def daoPackagePath   = 'src/main/java/ksbysample/webapp/lending/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()
}

task downloadCssFontsJs << {
    def staticDirPath   = 'src/main/resources/static'
    def workDirPath     = 'work'
    def adminLTEVersion     = '2.2.0'
    def jQueryVersion       = '2.1.4'
    def fontAwesomeVersion  = '4.3.0'
    def ioniconsVersion     = '2.0.1'
    def html5shivJsVersion  = '3.7.2'
    def respondMinJsVersion = '1.4.2'

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

    // Bootstrap & AdminLTE Dashboard & Control Panel Template
    downloadAdminLTE("${adminLTEVersion}", "${jQueryVersion}", "${workDirPath}", "${staticDirPath}")

    // Font Awesome Icons
    downloadFontAwesome("${fontAwesomeVersion}", "${workDirPath}", "${staticDirPath}")

    // Ionicons
    downloadIonicons("${ioniconsVersion}", "${workDirPath}", "${staticDirPath}")

    // html5shiv.js
    downloadHtml5shivJs("${html5shivJsVersion}", "${workDirPath}", "${staticDirPath}")

    // respond.min.js
    downloadRespondMinJs("${respondMinJsVersion}", "${workDirPath}", "${staticDirPath}")

    // fileinput.min.js ( v4.2.7 )
    downloadBootstrapFileInputMinJs("${workDirPath}", "${staticDirPath}")

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

    // 追加したファイルを git add する
    addGit()
}

task printClassWhatNotMakeTest << {
    def srcDir = new File("src/main/java");
    def excludePaths = [
            "src/main/java/ksbysample/webapp/lending/Application.java"
            , "src/main/java/ksbysample/webapp/lending/config"
            , "src/main/java/ksbysample/webapp/lending/cookie"
            , "src/main/java/ksbysample/webapp/lending/dao"
            , "src/main/java/ksbysample/webapp/lending/entity"
            , "src/main/java/ksbysample/webapp/lending/exception"
            , "src/main/java/ksbysample/webapp/lending/helper/download/booklistcsv"
            , "src/main/java/ksbysample/webapp/lending/helper/download/DataDownloadHelper.java"
            , "src/main/java/ksbysample/webapp/lending/helper/page/PagenationHelper.java"
            , "src/main/java/ksbysample/webapp/lending/security/LendingUser.java"
            , "src/main/java/ksbysample/webapp/lending/security/RoleAwareAuthenticationSuccessHandler.java"
            , "src/main/java/ksbysample/webapp/lending/service/calilapi/response"
            , "src/main/java/ksbysample/webapp/lending/service/file/BooklistCSVRecord.java"
            , "src/main/java/ksbysample/webapp/lending/service/openweathermapapi"
            , "src/main/java/ksbysample/webapp/lending/service/queue/InquiringStatusOfBookQueueMessage.java"
            , "src/main/java/ksbysample/webapp/lending/util/doma"
            , "src/main/java/ksbysample/webapp/lending/util/velocity/VelocityUtils.java"
            , "src/main/java/ksbysample/webapp/lending/values/validation/ValuesEnum.java"
            , "src/main/java/ksbysample/webapp/lending/view/BookListCsvView.java"
            , "src/main/java/ksbysample/webapp/lending/web/.+/.+Service.java"
            , "src/main/java/ksbysample/webapp/lending/webapi/common/CommonWebApiResponse.java"
            , "src/main/java/ksbysample/webapp/lending/webapi/weather"
    ];
    def excludeFileNamePatterns = [
            ".*EventListener.java"
            , ".*Dto.java"
            , ".*Form.java"
            , ".*Values.java"
    ];

    compareSrcAndTestDir(srcDir, excludePaths, excludeFileNamePatterns);
}

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

void addGit() {
    def grgit = org.ajoberstar.grgit.Grgit.open(dir: project.projectDir)
    grgit.add(patterns: ['.'])
}

void downloadAdminLTE(String adminLTEVersion, String jQueryVersion, String workDirPath, String staticDirPath) {
    download {
        src "https://codeload.github.com/almasaeed2010/AdminLTE/zip/v${adminLTEVersion}"
        dest new File("${workDirPath}/download/AdminLTE-${adminLTEVersion}.zip")
    }
    copy {
        from zipTree("${workDirPath}/download/AdminLTE-${adminLTEVersion}.zip")
        into "${workDirPath}/unzip"
    }
    copy {
        from "${workDirPath}/unzip/AdminLTE-${adminLTEVersion}/bootstrap/css"
        into "${staticDirPath}/css"
    }
    copy {
        from "${workDirPath}/unzip/AdminLTE-${adminLTEVersion}/bootstrap/fonts"
        into "${staticDirPath}/fonts"
    }
    copy {
        from "${workDirPath}/unzip/AdminLTE-${adminLTEVersion}/bootstrap/js"
        into "${staticDirPath}/js"
    }
    copy {
        from "${workDirPath}/unzip/AdminLTE-${adminLTEVersion}/dist/css"
        into "${staticDirPath}/css"
    }
    copy {
        from "${workDirPath}/unzip/AdminLTE-${adminLTEVersion}/dist/js"
        into "${staticDirPath}/js"
    }
    copy {
        from "${workDirPath}/unzip/AdminLTE-${adminLTEVersion}/plugins/jQuery/jQuery-${jQueryVersion}.min.js"
        into "${staticDirPath}/js"
    }
    delete "${staticDirPath}/js/pages"
    delete "${staticDirPath}/js/demo.js"
}

void downloadFontAwesome(String fontAwesomeVersion, String workDirPath, String staticDirPath) {
    download {
        src "http://fortawesome.github.io/Font-Awesome/assets/font-awesome-${fontAwesomeVersion}.zip"
        dest new File("${workDirPath}/download/font-awesome-${fontAwesomeVersion}.zip")
    }
    copy {
        from zipTree("${workDirPath}/download/font-awesome-${fontAwesomeVersion}.zip")
        into "${workDirPath}/unzip"
    }
    copy {
        from "${workDirPath}/unzip/font-awesome-${fontAwesomeVersion}/css/font-awesome.min.css"
        into "${staticDirPath}/css"
    }
    copy {
        from "${workDirPath}/unzip/font-awesome-${fontAwesomeVersion}/fonts"
        into "${staticDirPath}/fonts"
    }
}

void downloadIonicons(String ioniconsVersion, String workDirPath, String staticDirPath) {
    download {
        src "https://codeload.github.com/driftyco/ionicons/zip/v${ioniconsVersion}"
        dest new File("${workDirPath}/download/ionicons-${ioniconsVersion}.zip")
    }
    copy {
        from zipTree("${workDirPath}/download/ionicons-${ioniconsVersion}.zip")
        into "${workDirPath}/unzip"
    }
    copy {
        from "${workDirPath}/unzip/ionicons-${ioniconsVersion}/css/ionicons.min.css"
        into "${staticDirPath}/css"
    }
    copy {
        from "${workDirPath}/unzip/ionicons-${ioniconsVersion}/fonts"
        into "${staticDirPath}/fonts"
    }
}

void downloadHtml5shivJs(String html5shivJsVersion, String workDirPath, String staticDirPath) {
    download {
        src "https://oss.maxcdn.com/html5shiv/${html5shivJsVersion}/html5shiv.min.js"
        dest new File("${workDirPath}/download/html5shiv.min.js")
    }
    copy {
        from "${workDirPath}/download/html5shiv.min.js"
        into "${staticDirPath}/js"
    }
}

void downloadRespondMinJs(String respondMinJsVersion, String workDirPath, String staticDirPath) {
    download {
        src "https://oss.maxcdn.com/respond/${respondMinJsVersion}/respond.min.js"
        dest new File("${workDirPath}/download/respond.min.js")
    }
    copy {
        from "${workDirPath}/download/respond.min.js"
        into "${staticDirPath}/js"
    }
}

void downloadBootstrapFileInputMinJs(String workDirPath, String staticDirPath) {
    download {
        src "https://github.com/kartik-v/bootstrap-fileinput/zipball/master"
        dest new File("${workDirPath}/download/kartik-v-bootstrap-fileinput.zip")
    }
    copy {
        from zipTree("${workDirPath}/download/kartik-v-bootstrap-fileinput.zip")
        into "${workDirPath}/unzip"
    }
    copy {
        from "${workDirPath}/unzip/kartik-v-bootstrap-fileinput-883d8b6/js/fileinput.min.js"
        into "${staticDirPath}/js"
    }
    copy {
        from "${workDirPath}/unzip/kartik-v-bootstrap-fileinput-883d8b6/js/fileinput_locale_ja.js"
        into "${staticDirPath}/js"
    }
    copy {
        from "${workDirPath}/unzip/kartik-v-bootstrap-fileinput-883d8b6/css/fileinput.min.css"
        into "${staticDirPath}/css"
    }
}

def compareSrcAndTestDir(srcDir, excludePaths, excludeFileNamePatterns) {
    def existFlg
    def testDirAndTestClassNameList = [
            ["src/test/java", "Test.java"]
            , ["src/test/groovy", "Test.groovy"]
    ]

    for (srcFile in srcDir.listFiles()) {
        String srcFilePath = (srcFile.toPath() as String).replaceAll("\\\\", "/")
        existFlg = false

        for (exclude in excludePaths) {
            if (srcFilePath =~ /^${exclude as String}/) {
                existFlg = true
                break
            }
        }
        if (existFlg == true) continue

        for (exclude in excludeFileNamePatterns) {
            if (srcFilePath =~ /${exclude as String}/) {
                existFlg = true
                break
            }
        }
        if (existFlg == true) continue

        if (srcFile.isDirectory()) {
            compareSrcAndTestDir(srcFile, excludePaths, excludeFileNamePatterns)
        } else {
            def nextFlg = false
            for (testDirAndTestClassName in testDirAndTestClassNameList) {
                def testDir = testDirAndTestClassName[0]
                def testClassName = testDirAndTestClassName[1]

                String testFilePath = srcFilePath.replaceFirst(/^src\/main\/java/, testDir).replaceFirst(/\.java$/, testClassName)
                def testFile = new File(testFilePath)
                if (testFile.exists()) {
                    nextFlg = true
                    break
                }
            }
            if (nextFlg) continue

            println(srcFilePath);
        }
    }
}
  • Spring Boot のバージョンアップ対応として以下の点を変更します。
    • buildscript の以下の点を変更します。
      • springBootVersion = '1.3.5.RELEASE'springBootVersion = '1.4.4.RELEASE' に変更します。
      • classpath("io.spring.gradle:dependency-management-plugin:0.5.6.RELEASE")classpath("io.spring.gradle:dependency-management-plugin:0.6.1.RELEASE") に変更します。dependency-management-plugin は 1.0.0.RELEASE が出ていますが、これと spring-boot-gradle-plugin を一緒に入れると動かないので今回は 0.6.1.RELEASE を使用します。
    • apply plugin: 'spring-boot'apply plugin: 'org.springframework.boot' に変更します。
    • dependencyManagement の以下の点を変更します。
      • mavenBom 'io.spring.platform:platform-bom:2.0.5.RELEASE'mavenBom 'io.spring.platform:platform-bom:Athens-SR3' に変更します。
  • ライブラリを最新バージョンにアップデートするために以下の点を変更します。
    • buildscript の以下の点を変更します。
      • classpath("org.ajoberstar:grgit:1.7.0")classpath("org.ajoberstar:grgit:1.8.0") に変更します。
      • classpath("de.undercouch:gradle-download-task:3.0.0")classpath("de.undercouch:gradle-download-task:3.2.0") に変更します。
    • dependencies の以下の点を変更します。
      • def jdbcDriver = "org.postgresql:postgresql:9.4.1208"def jdbcDriver = "org.postgresql:postgresql:9.4.1212" に変更します。
      • compile("org.seasar.doma:doma:2.8.0")compile("org.seasar.doma:doma:2.15.0") に変更します。
      • compile("org.apache.commons:commons-lang3:3.4")compile("org.apache.commons:commons-lang3:3.5") に変更します。
      • compile("org.projectlombok:lombok:1.16.8")compileOnly("org.projectlombok:lombok:1.16.12") に変更します。
      • compile("com.google.guava:guava:19.0")compile("com.google.guava:guava:21.0") に変更します。
      • compile("com.univocity:univocity-parsers:2.1.1")compile("com.univocity:univocity-parsers:2.3.1") に変更します。
      • compile("org.thymeleaf.extras:thymeleaf-extras-java8time:2.1.0.RELEASE")compile("org.thymeleaf.extras:thymeleaf-extras-java8time:3.0.0.RELEASE") に変更します。
      • testCompile("org.dbunit:dbunit:2.5.1")testCompile("org.dbunit:dbunit:2.5.3") に変更します。
      • testCompile("com.icegreen:greenmail:1.5.0")testCompile("com.icegreen:greenmail:1.5.3") に変更します。
      • testCompile("org.assertj:assertj-core:3.4.1")testCompile("org.assertj:assertj-core:3.6.2") に変更します。
      • testCompile("org.jmockit:jmockit:1.23")testCompile("org.jmockit:jmockit:1.30") に変更します。
      • domaGenRuntime("org.seasar.doma:doma-gen:2.8.0")domaGenRuntime("org.seasar.doma:doma-gen:2.15.0") に変更します。
  • それ以外に以下の点を変更します。
    • compileJava.options.compilerArgs, compileTestGroovy.options.compilerArgs, compileTestJava.options.compilerArgs の3行を1行にして、-options,-processing を追加し、[compileJava, compileTestGroovy, compileTestJava]*.options*.compilerArgs = ['-Xlint:all,-options,-processing'] に変更します。
    • eclipse { ... } を削除します。
    • dependencies の以下の点を変更します。
      • testCompileOnly("org.projectlombok:lombok:1.16.12") を追加します。

■その2

sourceCompatibility = 1.8
targetCompatibility = 1.8

task wrapper(type: Wrapper) {
    gradleVersion = '2.13'
}
  • task wrapper(type: Wrapper) { gradleVersion = '2.13' } を追加します。

履歴

2017/02/11
初版発行。

Spring Boot 1.3.x の Web アプリを 1.4.x へバージョンアップする ( その2 )( IntelliJ IDEA の Gradle Tool Window の「Refresh all Gradle projects」を押してもエラーが出ないようにする )

概要

記事一覧はこちらです。

Spring Boot 1.3.x の Web アプリを 1.4.x へバージョンアップする ( その1 )( 概要 ) の続きです。

  • 今回の手順で確認できるのは以下の内容です。
    • IntelliJ IDEA の Gradle Tool Window の左上の「Refresh all Gradle projects」を押すとエラーが出たので、それを解消します。
    • 使用している IntelliJ IDEA のバージョンは 2016.3.4 です。

参照したサイト・書籍

目次

  1. 1.4.x ブランチの作成
  2. どんなエラーが出たのか?
  3. プロジェクトの JDK を設定し直す
  4. Gradle Tool Window で「Refresh all Gradle projects」ボタンを押して更新する

手順

1.4.x ブランチの作成

  1. master から 1.4.x ブランチを、1.4.x から feature/128-issue ブランチを作成します。

どんなエラーが出たのか?

IntelliJ IDEA をアップデート後1度も ksbysample-webapp-lending プロジェクトを開いていませんでした。開くと Gradle Tool Window に other のツリーしか表示されていない状態でした。

f:id:ksby:20170208224535p:plain

左上の「Refresh all Gradle projects」ボタンを押して更新すると以下のエラーが表示されます。

f:id:ksby:20170208224826p:plain

エラーメッセージの中に “Project JDK is not specified.” と出力されていました。そう言えば JDK をバージョンアップすると古い JDK を削除していましたね。。。 JDK を設定し直せば解消しそうです。

プロジェクトの JDK を設定し直す

  1. IntelliJ IDEA のメインメニューから「File」-「Project Structure…」を選択します。

  2. 「Project Structure」ダイアログが表示されます。「Project SDK」が赤字で表示されており、「Project language level」も「1.3 - Plain old Java」になっていました。

    f:id:ksby:20170208225702p:plain

    「Project SDK」で「1.8.0_121」を選択し、「Project language level」で「SDK default」を選択した後、「OK」ボタンを押してダイアログを閉じます。

    f:id:ksby:20170208225818p:plain

Gradle Tool Window で「Refresh all Gradle projects」ボタンを押して更新する

  1. Gradle Tool Window で「Refresh all Gradle projects」ボタンを押して更新してみます。

    今度は無事更新されて、application や build のツリーが表示されました。

    f:id:ksby:20170208232010p:plain

  2. clean タスク実行 → Rebuild Project 実行をした後、build タスクを実行して “BUILD SUCCESSFUL” のメッセージが表示されることを確認します。

    groovy 関連のライブラリも結構ダウンロードされますね。

    f:id:ksby:20170208232917p:plain f:id:ksby:20170208233115p:plain

  3. Project Tool Window で src/test を選択した後、コンテキストメニューを表示して「Run ‘All Tests’ with Coverage」を選択し、テストが全て成功することを確認します。

    f:id:ksby:20170208233950p:plain

正常に動作することが確認できましたので、次回は build.gradle を修正します。

履歴

2017/02/08
初版発行。

Spring Boot 1.3.x の Web アプリを 1.4.x へバージョンアップする ( その1 )( 概要 )

概要

記事一覧はこちらです。

  • 「Spring Boot で書籍の貸出状況確認・貸出申請する Web アプリケーションを作る」で作成した Web アプリケーション ( ksbysample-webapp-lending ) の Spring Boot のバージョンを 1.3.5 → 1.4.4 へバージョンアップします。
  • 進め方は以下の方針とします。
    • Git のブランチは 1.4.x を作成して、そちらで作業します。Spring Boot のバージョンと合わせます。
    • 最初に IntelliJ IDEA をバージョンアップしているためか Gradle Tool Window の左上の「Refresh all Gradle projects」を押すとエラーが出たので、それを解消します。
    • 次に build.gradle を修正します。
      • Spring Boot のバージョン番号を 1.4.4 に、Spring IO Platform の BOM を Athens-SR3 にします。
      • Spring Initializr で 1.4.4 のプロジェクトを作成して、修正した方がよさそうな点があれば反映します。
      • ライブラリは最新バージョンにアップデートします。
    • プロジェクトを build し直してエラーが出る点があれば修正し、まずはここまでで動くようにします。
    • その後で 1.4 系ではこう書くべきという点があるか確認し、変更した方がよいところを修正します。

 
1.4 のリリースノートはこちらです。

Spring Boot 1.4 Release Notes
https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-1.4-Release-Notes

すでに 1.5.1 がリリースされていますので、手短に 1.4 系へのバージョンアップをまとめられるといいな。。。

履歴

2017/02/08
初版発行。