かんがるーさんの日記

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

Spring Boot + npm + Geb で入力フォームを作ってテストする ( その60 )( 確認画面を作成する3 )

概要

記事一覧はこちらです。

Spring Boot + npm + Geb で入力フォームを作ってテストする ( その59 )( 確認画面を作成する2 ) の続きです。

  • 今回の手順で確認できるのは以下の内容です。
    • 確認画面の作成
    • confirm.js を見たら「修正する」ボタンは実装済で変更する必要もなかったので、「送信する」ボタンを実装します。
    • 今回はメール送信処理を書きます。

参照したサイト・書籍

目次

  1. メールを送信する処理を実装する
    1. InquiryMailHelper クラスを作成する
    2. メールのテンプレートを作成する
    3. InquiryConfirmService クラスを変更する
    4. 動作確認
  2. 次は。。。

手順

メールを送信する処理を実装する

InquiryMailHelper クラスを作成する

src/main/java/ksbysample/webapp/bootnpmgeb/helper/mail の下に InquiryMailHelper.java を新規作成し、以下の内容を記述します。

package ksbysample.webapp.bootnpmgeb.helper.mail;

import ksbysample.webapp.bootnpmgeb.helper.freemarker.FreeMarkerHelper;
import ksbysample.webapp.bootnpmgeb.web.inquiry.form.ConfirmForm;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.stereotype.Component;

import javax.mail.MessagingException;
import javax.mail.internet.MimeMessage;
import java.util.HashMap;
import java.util.Map;

/**
 * 問い合わせフォームのメール用 Helper クラス
 */
@SuppressWarnings("PMD.AvoidThrowingRawExceptionTypes")
@Component
public class InquiryMailHelper {

    private static final String TEMPLATE_LOCATION_TEXTMAIL = "mail/inquirymail-body.ftl";

    private static final String FROM_ADDR = "inquiry-form@sample.co.jp";
    private static final String TO_ADDR = "inquiry@sample.co.jp";
    private static final String SUBJECT = "問い合わせフォームからお問い合わせがありました";

    private final FreeMarkerHelper freeMarkerHelper;

    private final JavaMailSender mailSender;

    /**
     * コンストラクタ
     *
     * @param freeMarkerHelper {@FreeMarkerHelper} オブジェクト
     * @param mailSender       {@JavaMailSender} オブジェクト
     */
    public InquiryMailHelper(FreeMarkerHelper freeMarkerHelper
            , JavaMailSender mailSender) {
        this.freeMarkerHelper = freeMarkerHelper;
        this.mailSender = mailSender;
    }

    /**
     * {@MimeMessage} オブジェクトを生成する
     * メール本文は入力画面1~3で入力された内容から生成する
     *
     * @param confirmForm {@ConfirmForm} オブジェクト
     * @return {@MimeMessage} オブジェクト
     */
    public MimeMessage createMessage(ConfirmForm confirmForm) {
        try {
            MimeMessage mimeMessage = mailSender.createMimeMessage();
            MimeMessageHelper message = new MimeMessageHelper(mimeMessage, false, "UTF-8");
            message.setFrom(FROM_ADDR);
            message.setTo(TO_ADDR);
            message.setSubject(SUBJECT);
            message.setText(generateTextUsingVelocity(confirmForm), false);
            return message.getMimeMessage();
        } catch (MessagingException e) {
            throw new RuntimeException(e);
        }
    }

    private String generateTextUsingVelocity(ConfirmForm confirmForm) {
        Map<String, Object> model = new HashMap<>();
        model.put("confirmForm", confirmForm);
        return freeMarkerHelper.merge(TEMPLATE_LOCATION_TEXTMAIL, model);
    }

}

メールのテンプレートを作成する

src/main/resources/templates の下に mail ディレクトリを新規作成します。

src/main/resources/templates/mail の下に inquirymail-body.ftl を新規作成し、以下の内容を記述します。

問い合わせフォームから入力された内容は以下の通りです。

お名前(漢字):${confirmForm.name!}
お名前(かな):${confirmForm.kana!}
性別:${confirmForm.sex!}
年齢:${confirmForm.age!}歳
職業:${confirmForm.job!}
郵便番号:〒${confirmForm.zipcode!}
住所:${confirmForm.address!}
電話番号:${confirmForm.tel!}
メールアドレス:${confirmForm.email!}

お問い合わせの種類1:${confirmForm.type1!}
お問い合わせの種類2:${confirmForm.type2!}
お問い合わせの内容:
${confirmForm.inquiry!}
アンケート:
<#list confirmForm.survey as sv>
・${sv!}
</#list>

InquiryConfirmService クラスを変更する

src/main/java/ksbysample/webapp/bootnpmgeb/web/inquiry/InquiryConfirmService.java の以下の点を変更します。

package ksbysample.webapp.bootnpmgeb.web.inquiry;

import ksbysample.webapp.bootnpmgeb.dao.InquiryDataDao;
import ksbysample.webapp.bootnpmgeb.entity.InquiryData;
import ksbysample.webapp.bootnpmgeb.helper.mail.EmailHelper;
import ksbysample.webapp.bootnpmgeb.helper.mail.InquiryMailHelper;
import ksbysample.webapp.bootnpmgeb.session.SessionData;
import ksbysample.webapp.bootnpmgeb.web.inquiry.form.ConfirmForm;
import org.modelmapper.ModelMapper;
import org.springframework.stereotype.Service;

import javax.mail.internet.MimeMessage;

/**
 * 確認画面用 Service クラス
 */
@Service
public class InquiryConfirmService {

    private final InquiryDataDao inquiryDataDao;

    private final ModelMapper modelMapper;

    private final InquiryMailHelper inquiryMailHelper;

    private final EmailHelper emailHelper;

    /**
     * コンストラクタ
     *
     * @param inquiryDataDao    {@InquiryDataDao} オブジェクト
     * @param modelMapper       {@ModelMapper} オブジェクト
     * @param inquiryMailHelper {@InquiryMailHelper} オブジェクト
     * @param emailHelper       {@EmailHelper} オブジェクト
     */
    public InquiryConfirmService(InquiryDataDao inquiryDataDao
            , ModelMapper modelMapper
            , InquiryMailHelper inquiryMailHelper
            , EmailHelper emailHelper) {
        this.inquiryDataDao = inquiryDataDao;
        this.modelMapper = modelMapper;
        this.inquiryMailHelper = inquiryMailHelper;
        this.emailHelper = emailHelper;
    }

    /**
     * 入力されたデータを INQUIRY_DATA テーブルに保存してメールを送信する
     *
     * @param sessionData {@SessionData} オブジェクト
     * @param confirmForm {@ConfirmForm} オブジェクト
     */
    public void saveToDbAndSendMail(SessionData sessionData, ConfirmForm confirmForm) {
        // INQUIRY_DATA テーブルに保存する
        InquiryData inquiryData = modelMapper.map(sessionData, InquiryData.class);
        inquiryDataDao.insert(inquiryData);

        // メールを送信する
        MimeMessage message = inquiryMailHelper.createMessage(confirmForm);
        emailHelper.sendMail(message);
    }

}
  • フィールドに以下の2行を追加します。コンストラクタにセットする処理も追加します。
    • private final InquiryMailHelper inquiryMailHelper;
    • private final EmailHelper emailHelper;
  • saveToDbAndSendMail メソッドに以下の2行を追加します。
    • MimeMessage message = inquiryMailHelper.createMessage(confirmForm);
    • emailHelper.sendMail(message);

動作確認

動作確認します。npm run springboot コマンドを実行し Tomcat を起動した後、ブラウザで http://localhost:9080/inquiry/input/01/ にアクセスします。今回はメールを送信するので smtp4dev も起動します。

入力画面1~3に以下の画像のデータを入力します。

f:id:ksby:20180617222427p:plain f:id:ksby:20180617222540p:plain f:id:ksby:20180617222726p:plain

確認画面に以下のように表示されますので、「送信する」ボタンをクリックします。

f:id:ksby:20180617222837p:plain f:id:ksby:20180617222918p:plain

完了画面が表示されます。

f:id:ksby:20180617223019p:plain

smtp4dev の画面を見るとメールが1通送信されています。「Inspect」ボタンをクリックしてメールの内容を確認します。

f:id:ksby:20180617223259p:plain

ヘッダとメール本文は問題なさそうです。

f:id:ksby:20180617223613p:plain f:id:ksby:20180617223142p:plain

「職業」「電話番号」「アンケート」を未入力・未選択にしてメールを送信しても、項目が空になるだけでエラーにはなりません。

f:id:ksby:20180617224007p:plain

次は。。。

確認画面のテストを作成します。

履歴

2018/06/17
初版発行。

IntelliJ IDEA を 2018.1.4 → 2018.1.5 へ、Git for Windows を 2.17.0 → 2.17.1(2) へバージョンアップ

IntelliJ IDEA を 2018.1.4 → 2018.1.5 へバージョンアップする

IntelliJ IDEA の 2018.1.5 がリリースされているのでバージョンアップします。

※ksbysample-webapp-lending プロジェクトを開いた状態でバージョンアップしています。

  1. IntelliJ IDEA のメインメニューから「Help」-「Check for Updates...」を選択します。

  2. IDE and Plugin Updates」ダイアログが表示されます。左下に「Update and Restart」ボタンが表示されていますので、「Update and Restart」ボタンをクリックします。

    f:id:ksby:20180617143333p:plain

  3. Plugin の update も表示されました。このまま「Update and Restart」ボタンをクリックします。

    f:id:ksby:20180617143436p:plain

  4. Patch がダウンロードされて IntelliJ IDEA が再起動します。

  5. IntelliJ IDEA が起動すると画面下部に「Indexing…」のメッセージが表示されますので、終了するまで待機します。

    f:id:ksby:20180617144105p:plain

  6. IntelliJ IDEA のメインメニューから「Help」-「About」を選択し、2018.1.5 へバージョンアップされていることを確認します。

  7. Gradle Tool Window のツリーを見ると「Tasks」の下に「other」しかない状態になっているので、左上にある「Refresh all Gradle projects」ボタンをクリックして更新します。

    f:id:ksby:20180617144300p:plain

  8. clean タスク実行 → Rebuild Project 実行 → build タスクを実行して、"BUILD SUCCESSFUL" のメッセージが出力されることを確認します。

    f:id:ksby:20180617144928p:plain

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

    f:id:ksby:20180617145405p:plain

Git for Windows を 2.17.0 → 2.17.1(2) へバージョンアップする

Git for Windows の 2.17.1(2) がリリースされていたのでバージョンアップします。

  1. https://git-for-windows.github.io/ の「Download」ボタンをクリックして Git-2.17.1.2-64-bit.exe をダウンロードします。

  2. Git-2.17.1.2-64-bit.exe を実行します。

  3. 「Git 2.17.1.2 Setup」ダイアログが表示されます。[Next >]ボタンをクリックします。

  4. 「Select Components」画面が表示されます。「Git LFS(Large File Support)」だけチェックした状態で [Next >]ボタンをクリックします。

  5. 「Choosing the default editor used by Git」画面が表示されます。「Use Vim (the ubiquitous text editor) as Git's default editor」が選択された状態で [Next >]ボタンをクリックします。

  6. 「Adjusting your PATH environment」画面が表示されます。中央の「Use Git from the Windows Command Prompt」が選択されていることを確認後、[Next >]ボタンをクリックします。

  7. 「Choosing HTTPS transport backend」画面が表示されます。「Use the OpenSSL library」が選択されていることを確認後、[Next >]ボタンをクリックします。

  8. 「Configuring the line ending conversions」画面が表示されます。一番上の「Checkout Windows-style, commit Unix-style line endings」が選択されていることを確認した後、[Next >]ボタンをクリックします。

  9. 「Configuring the terminal emulator to use with Git Bash」画面が表示されます。「Use Windows'default console window」が選択されていることを確認した後、[Next >]ボタンをクリックします。

  10. 「Configuring extra options」画面が表示されます。「Enable file system caching」だけがチェックされていることを確認した後、[Install]ボタンをクリックします。

  11. インストールが完了すると「Completing the Git Setup Wizard」のメッセージが表示された画面が表示されます。中央の「View Release Notes」のチェックを外した後、「Finish」ボタンをクリックしてインストーラーを終了します。

  12. コマンドプロンプトを起動して git --version を実行し、git のバージョンが git version 2.17.1.windows.2 になっていることを確認します。

    f:id:ksby:20180617150503p:plain

  13. git-cmd.exe を起動して日本語の表示・入力が問題ないかを確認します。

    f:id:ksby:20180617150615p:plain

  14. 特に問題はないようですので、2.17.1(2) で作業を進めたいと思います。

Spring Boot + npm + Geb で入力フォームを作ってテストする ( その59 )( 確認画面を作成する2 )

概要

記事一覧はこちらです。

Spring Boot + npm + Geb で入力フォームを作ってテストする ( その58 )( 確認画面を作成する ) の続きです。

  • 今回の手順で確認できるのは以下の内容です。
    • 確認画面の作成
    • confirm.js を見たら「修正する」ボタンは実装済で変更する必要もなかったので、「送信する」ボタンを実装します。
    • 2回に分けます。1回目は DB保存処理、2回目はメール送信処理を書きます。

参照したサイト・書籍

目次

  1. DB に保存する処理を実装する
    1. INQUIRY_DATA テーブルを変更する
    2. ModelMapper で使用する SessionData → InquiryData データ変換用クラスを作成する
    3. InquiryConfirmService クラスを作成する
    4. InquiryConfirmController クラスを変更する
    5. 動作確認

手順

DB に保存する処理を実装する

INQUIRY_DATA テーブルを変更する

INQUIRY_DATA テーブルの以下の点を変更します。

  • type2 には入力画面3の「お問い合わせの種類2」で選択された項目の値を "," 区切りで結合した文字列を保存するようにします。また必須項目にしたので NOT NULL を追加します。VARCHAR(1)VARCHAR(32) NOT NULL に変更します。
  • survey には入力画面3の「アンケート」で選択された項目の値を "," 区切りで結合した文字列を保存するようにします。また任意項目にしたので NOT NULL を削除します。VARCHAR(1) NOT NULLVARCHAR(32) に変更します。

今回は V1__init.sql はそのままで、V1.1__Alter_column.sql を追加してみます。src/main/resources/db/migration の下に V1.1__Alter_column.sql を新規作成し、以下の内容を記述します。

ALTER TABLE INQUIRY_DATA ALTER COLUMN type2 VARCHAR(32) NOT NULL;
ALTER TABLE INQUIRY_DATA ALTER COLUMN survey VARCHAR(32);

動作確認します。Tomcat を起動した後、IntelliJ IDEA の Database Tools で「Synchronize」ボタンを押して更新し、カラムの定義が変更されていることを確認します。

f:id:ksby:20180616200648p:plain

ModelMapper で使用する SessionData → InquiryData データ変換用クラスを作成する

まず今回は INQUIRY_DATA テーブルの Entity クラス ksbysample.webapp.bootnpmgeb.entity.InquiryData に Clob 型を使用しているので、

@Entity
@Table(name = "INQUIRY_DATA")
public class InquiryData {

    ..........

    @Column(name = "INQUIRY")
    Clob inquiry;

    ..........
}

CLOB を使用するためのメソッドを Dao インターフェースに追加します。src/main/java/ksbysample/webapp/bootnpmgeb/dao/InquiryDataDao.java の以下の点を変更します。

@Dao
@ComponentAndAutowiredDomaConfig
public interface InquiryDataDao {

    ..........

    /**
     * Clob 生成用
     * @return {@Clob} オブジェクト
     */
    @ClobFactory
    Clob createClob();

}
  • @ClobFactory Clob createClob(); を追加します。

次に src/main/java/ksbysample/webapp/bootnpmgeb/mapper の下に SessionData2InquiryDataTypeMap.java を新規作成し、以下の内容を記述します。

package ksbysample.webapp.bootnpmgeb.mapper;

import com.github.rozidan.springboot.modelmapper.TypeMapConfigurer;
import ksbysample.webapp.bootnpmgeb.dao.InquiryDataDao;
import ksbysample.webapp.bootnpmgeb.entity.InquiryData;
import ksbysample.webapp.bootnpmgeb.session.SessionData;
import ksbysample.webapp.bootnpmgeb.web.inquiry.form.InquiryInput03Form;
import org.modelmapper.TypeMap;
import org.springframework.stereotype.Component;

import java.sql.Clob;
import java.sql.SQLException;
import java.time.LocalDateTime;
import java.util.stream.Collectors;

/**
 * SessionData --> InquiryData データ変換用クラス
 */
@SuppressWarnings({"checkstyle:LineLength", "PMD.AvoidThrowingRawExceptionTypes"})
@Component
public class SessionData2InquiryDataTypeMap extends TypeMapConfigurer<SessionData, InquiryData> {

    private final InquiryDataDao inquiryDataDao;

    /**
     * コンストラクタ
     *
     * @param inquiryDataDao {@InquiryDataDao} オブジェクト
     */
    public SessionData2InquiryDataTypeMap(InquiryDataDao inquiryDataDao) {
        super();
        this.inquiryDataDao = inquiryDataDao;
    }

    @Override
    public void configure(TypeMap<SessionData, InquiryData> typeMap) {
        typeMap.setPreConverter(context -> {
            SessionData sessionData = context.getSource();
            InquiryInput03Form inquiryInput03Form = sessionData.getInquiryInput03Form();
            InquiryData inquiryData = context.getDestination();

            inquiryData.setType2(inquiryInput03Form.getType2().stream().collect(Collectors.joining(",")));
            Clob inquiryClob = inquiryDataDao.createClob();
            try {
                inquiryClob.setString(1, inquiryInput03Form.getInquiry());
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
            inquiryData.setInquiry(inquiryClob);
            inquiryData.setSurvey(inquiryInput03Form.getSurvey().stream().collect(Collectors.joining(",")));
            inquiryData.setUpdateDate(LocalDateTime.now());

            return context.getDestination();
        });

        typeMap.addMappings(mapping -> mapping.map(src -> src.getInquiryInput01Form().getLastname(), InquiryData::setLastname))
                .addMappings(mapping -> mapping.map(src -> src.getInquiryInput01Form().getFirstname(), InquiryData::setFirstname))
                .addMappings(mapping -> mapping.map(src -> src.getInquiryInput01Form().getLastkana(), InquiryData::setLastkana))
                .addMappings(mapping -> mapping.map(src -> src.getInquiryInput01Form().getFirstkana(), InquiryData::setFirstkana))
                .addMappings(mapping -> mapping.map(src -> src.getInquiryInput01Form().getSex(), InquiryData::setSex))
                .addMappings(mapping -> mapping.map(src -> src.getInquiryInput01Form().getAge(), InquiryData::setAge))
                .addMappings(mapping -> mapping.map(src -> src.getInquiryInput01Form().getJob(), InquiryData::setJob))
                .addMappings(mapping -> mapping.map(src -> src.getInquiryInput02Form().getZipcode1(), InquiryData::setZipcode1))
                .addMappings(mapping -> mapping.map(src -> src.getInquiryInput02Form().getZipcode2(), InquiryData::setZipcode2))
                .addMappings(mapping -> mapping.map(src -> src.getInquiryInput02Form().getAddress(), InquiryData::setAddress))
                .addMappings(mapping -> mapping.map(src -> src.getInquiryInput02Form().getTel1(), InquiryData::setTel1))
                .addMappings(mapping -> mapping.map(src -> src.getInquiryInput02Form().getTel2(), InquiryData::setTel2))
                .addMappings(mapping -> mapping.map(src -> src.getInquiryInput02Form().getTel3(), InquiryData::setTel3))
                .addMappings(mapping -> mapping.map(src -> src.getInquiryInput02Form().getEmail(), InquiryData::setEmail))
                .addMappings(mapping -> mapping.map(src -> src.getInquiryInput03Form().getType1(), InquiryData::setType1))
                .addMappings(mapping -> mapping.skip(InquiryData::setType2))
                .addMappings(mapping -> mapping.skip(InquiryData::setInquiry))
                .addMappings(mapping -> mapping.skip(InquiryData::setSurvey))
                .addMappings(mapping -> mapping.skip(InquiryData::setUpdateDate));
    }

}
  • .addMappings(mapping -> mapping.map(src -> src.getInquiryInput01Form().getFirstname(), InquiryData::set~)) の行が 120文字を超えているのですが、改行したくないので @SuppressWarnings"checkstyle:LineLength" を付加しています。
  • Clob::setString メソッドは SQLException を throw する可能性があるので try-catch 文を記述していますが、throw された SQLExceptionRuntimeException で throw することにします。このままだと PMD で警告が出るので @SuppressWarnings"PMD.AvoidThrowingRawExceptionTypes" を付加しています(実際には適切な例外クラスを定義して使用しましょう)。
  • typeMap.setPreConverter( ... ) 内には個別の変換処理が必要なものだけ記述し、単に Getter メソッドで取得して Setter メソッドでセットすればよいものは .addMappings(mapping -> mapping.map(src -> src.getInquiryInput01Form().getFirstname(), InquiryData::set~)) で記述します。

今の config/checkstyle/google_checks.xml の設定では @SuppressWarnings が反映されないので、以下の点を変更します。

<module name="Checker">

    ..........

    <module name="SuppressWarningsFilter"/>

    <module name="TreeWalker">
        <module name="SuppressWarningsHolder"/>
        <module name="OuterTypeFilename"/>
        ..........
  • 以下の2行を追加します。
    • <module name="SuppressWarningsFilter"/>
    • <module name="SuppressWarningsHolder"/>

InquiryConfirmService クラスを作成する

src/main/java/ksbysample/webapp/bootnpmgeb/web/inquiry の下に InquiryConfirmService.java を新規作成し、以下の内容を記述します。

package ksbysample.webapp.bootnpmgeb.web.inquiry;

import ksbysample.webapp.bootnpmgeb.dao.InquiryDataDao;
import ksbysample.webapp.bootnpmgeb.entity.InquiryData;
import ksbysample.webapp.bootnpmgeb.session.SessionData;
import ksbysample.webapp.bootnpmgeb.web.inquiry.form.ConfirmForm;
import org.modelmapper.ModelMapper;
import org.springframework.stereotype.Service;

/**
 * 確認画面用 Service クラス
 */
@Service
public class InquiryConfirmService {

    private final InquiryDataDao inquiryDataDao;
    private final ModelMapper modelMapper;

    /**
     * コンストラクタ
     *
     * @param inquiryDataDao {@InquiryDataDao} オブジェクト
     * @param modelMapper    {@ModelMapper} オブジェクト
     */
    public InquiryConfirmService(InquiryDataDao inquiryDataDao
            , ModelMapper modelMapper) {
        this.inquiryDataDao = inquiryDataDao;
        this.modelMapper = modelMapper;
    }

    /**
     * 入力されたデータを INQUIRY_DATA テーブルに保存してメールを送信する
     *
     * @param sessionData {@SessionData} オブジェクト
     * @param confirmForm {@ConfirmForm} オブジェクト
     */
    public void saveToDbAndSendMail(SessionData sessionData, ConfirmForm confirmForm) {
        // INQUIRY_DATA テーブルに保存する
        InquiryData inquiryData = modelMapper.map(sessionData, InquiryData.class);
        inquiryDataDao.insert(inquiryData);

        // メールを送信する
    }

}

InquiryConfirmController クラスを変更する

src/main/java/ksbysample/webapp/bootnpmgeb/web/inquiry/InquiryConfirmController.java の以下の点を変更します。

@Controller
@RequestMapping("/inquiry/confirm")
@SessionAttributes("sessionData")
public class InquiryConfirmController {

    ..........

    private final ModelMapper modelMapper;
    private final InquiryConfirmService inquiryConfirmService;

    /**
     * コンストラクタ
     *
     * @param modelMapper           {@ModelMapper} オブジェクト
     * @param inquiryConfirmService {@InquiryConfirmService} オブジェクト
     */
    public InquiryConfirmController(ModelMapper modelMapper
            , InquiryConfirmService inquiryConfirmService) {
        this.modelMapper = modelMapper;
        this.inquiryConfirmService = inquiryConfirmService;
    }

    ..........

    /**
     * 確認画面 「送信する」ボタンクリック時の処理
     *
     * @param sessionData {@SessionData} オブジェクト
     * @param builder     {@UriComponentsBuilder} オブジェクト
     * @return 完了画面の URL
     */
    @PostMapping("/send")
    public String send(SessionData sessionData
            , UriComponentsBuilder builder) {
        ConfirmForm confirmForm = modelMapper.map(sessionData, ConfirmForm.class);
        inquiryConfirmService.saveToDbAndSendMail(sessionData, confirmForm);

        return UrlBasedViewResolver.REDIRECT_URL_PREFIX
                + builder.path(UrlConst.URL_INQUIRY_COMPLETE).toUriString();
    }

}
  • private final InquiryConfirmService inquiryConfirmService; を追加します。合わせてコンストラクタにセットする処理を追加します。
  • send メソッドの引数に SessionData sessionData を追加します。
  • send メソッド内に以下の2行を追加します。confirmForm はメールの送信処理で使用します。
    • ConfirmForm confirmForm = modelMapper.map(sessionData, ConfirmForm.class);
    • inquiryConfirmService.saveToDbAndSendMail(sessionData, confirmForm);

動作確認

動作確認します。npm run springboot コマンドを実行し Tomcat を起動した後、ブラウザで http://localhost:9080/inquiry/input/01/ にアクセスします。

入力画面1~3に以下の画像のデータを入力します。

f:id:ksby:20180617123513p:plain f:id:ksby:20180617123602p:plain f:id:ksby:20180617123651p:plain

確認画面に以下のように表示されますので、「送信する」ボタンをクリックします。

f:id:ksby:20180617123817p:plain f:id:ksby:20180617123910p:plain

完了画面が表示されます。

f:id:ksby:20180617124107p:plain

IntelliJ IDEA の Database Tools から INQUIRY_DATA テーブルのデータを確認すると、問題なく保存されていることが確認できます。

f:id:ksby:20180617124232p:plain

履歴

2018/06/17
初版発行。

Spring Boot + npm + Geb で入力フォームを作ってテストする ( その58 )( 確認画面を作成する )

概要

記事一覧はこちらです。

Spring Boot + npm + Geb で入力フォームを作ってテストする ( その57 )( build.gradle に記述する BOM を Spring IO Platform のものから Spring Boot のものに変更する ) の続きです。

  • 今回の手順で確認できるのは以下の内容です。
    • 確認画面の作成
    • 今回は入力されたデータを画面に表示する処理を作成します。保存された入力値を画面に表示する文字列に変換する処理を ModelMapper のデータ変換用クラス内で行います。

参照したサイト・書籍

目次

  1. Form クラスを作成する
  2. ModelMapper で使用するデータ変換用クラスを作成する
  3. InquiryConfirmController クラスを変更する
  4. confirm.html を変更する
  5. 動作確認
  6. 次は。。。

手順

Form クラスを作成する

src/main/java/ksbysample/webapp/bootnpmgeb/web/inquiry/form の下に ConfirmForm.java を新規作成し、以下の内容を記述します。

package ksbysample.webapp.bootnpmgeb.web.inquiry.form;

import lombok.Data;

/**
 * 確認画面用 Form クラス
 */
@Data
public class ConfirmForm {

    /************************
     * 入力画面1の入力項目用
     ************************/
    private String name;

    private String kana;

    private String sex;

    private String age;

    private String job;

    /************************
     * 入力画面2の入力項目用
     ************************/
    private String zipcode;

    private String address;

    private String tel;

    private String email;

    /************************
     * 入力画面3の入力項目用
     ************************/
    private String type1;

    private String type2;

    private String inquiry;

    private List<String> survey;

}

ModelMapper で使用するデータ変換用クラスを作成する

データ変換のルールを定義するクラスを配置するパッケージを作成します。src/main/java/ksbysample/webapp/bootnpmgeb の下に mapper パッケージを作成します。

src/main/java/ksbysample/webapp/bootnpmgeb/mapper の下に SessionData2ConfirmFormTypeMap.java を新規作成し、以下の内容を記述します。

package ksbysample.webapp.bootnpmgeb.mapper;

import com.github.rozidan.springboot.modelmapper.TypeMapConfigurer;
import ksbysample.webapp.bootnpmgeb.helper.db.SurveyOptionsHelper;
import ksbysample.webapp.bootnpmgeb.session.SessionData;
import ksbysample.webapp.bootnpmgeb.values.*;
import ksbysample.webapp.bootnpmgeb.web.inquiry.form.ConfirmForm;
import ksbysample.webapp.bootnpmgeb.web.inquiry.form.InquiryInput01Form;
import ksbysample.webapp.bootnpmgeb.web.inquiry.form.InquiryInput02Form;
import ksbysample.webapp.bootnpmgeb.web.inquiry.form.InquiryInput03Form;
import org.apache.commons.lang3.StringUtils;
import org.modelmapper.TypeMap;
import org.springframework.stereotype.Component;

import java.util.Arrays;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * SessionData --> ConfirmForm データ変換用クラス
 */
@Component
public class SessionData2ConfirmFormTypeMap extends TypeMapConfigurer<SessionData, ConfirmForm> {

    private final ValuesHelper vh;

    private final SurveyOptionsHelper soh;

    /**
     * コンストラクタ
     *
     * @param vh  {@ValuesHelper} オブジェクト
     * @param soh {@SurveyOptionsHelper} オブジェクト
     */
    public SessionData2ConfirmFormTypeMap(ValuesHelper vh
            , SurveyOptionsHelper soh) {
        super();
        this.vh = vh;
        this.soh = soh;
    }

    @Override
    public void configure(TypeMap<SessionData, ConfirmForm> typeMap) {
        typeMap.setPreConverter(context -> {
            SessionData sessionData = context.getSource();
            InquiryInput01Form inquiryInput01Form = sessionData.getInquiryInput01Form();
            InquiryInput02Form inquiryInput02Form = sessionData.getInquiryInput02Form();
            InquiryInput03Form inquiryInput03Form = sessionData.getInquiryInput03Form();
            ConfirmForm confirmForm = context.getDestination();

            /************************
             * 入力画面1の入力項目用
             ************************/
            confirmForm.setName(join(" "
                    , inquiryInput01Form.getLastname()
                    , inquiryInput01Form.getFirstname()));
            confirmForm.setKana(join(" "
                    , inquiryInput01Form.getLastkana()
                    , inquiryInput01Form.getFirstkana()));
            confirmForm.setSex(vh.getText(SexValues.class, inquiryInput01Form.getSex()));
            confirmForm.setAge(inquiryInput01Form.getAge());
            confirmForm.setJob(vh.getText(JobValues.class, inquiryInput01Form.getJob()));

            /************************
             * 入力画面2の入力項目用
             ************************/
            confirmForm.setZipcode(join("-"
                    , inquiryInput02Form.getZipcode1()
                    , inquiryInput02Form.getZipcode2()));
            confirmForm.setAddress(inquiryInput02Form.getAddress());
            confirmForm.setTel(join("-"
                    , inquiryInput02Form.getTel1()
                    , inquiryInput02Form.getTel2()
                    , inquiryInput02Form.getTel3()));
            confirmForm.setEmail(inquiryInput02Form.getEmail());

            /************************
             * 入力画面3の入力項目用
             ************************/
            confirmForm.setType1(vh.getText(Type1Values.class, inquiryInput03Form.getType1()));
            confirmForm.setType2(inquiryInput03Form.getType2().stream()
                    .map(type2 -> vh.getText(Type2Values.class, type2))
                    .collect(Collectors.joining("、")));
            confirmForm.setInquiry(inquiryInput03Form.getInquiry());
            Map<String, String> surveyOptionsMap = this.soh.selectItemList("survey").stream()
                    .collect(Collectors.toMap(s -> s.getItemValue(), s -> s.getItemName()));
            confirmForm.setSurvey(inquiryInput03Form.getSurvey().stream()
                    .map(survey -> surveyOptionsMap.get(survey))
                    .collect(Collectors.toList()));

            return context.getDestination();
        });

        typeMap.addMappings(mapping -> mapping.skip(ConfirmForm::setName))
                .addMappings(mapping -> mapping.skip(ConfirmForm::setKana))
                .addMappings(mapping -> mapping.skip(ConfirmForm::setSex))
                .addMappings(mapping -> mapping.skip(ConfirmForm::setAge))
                .addMappings(mapping -> mapping.skip(ConfirmForm::setJob))
                .addMappings(mapping -> mapping.skip(ConfirmForm::setZipcode))
                .addMappings(mapping -> mapping.skip(ConfirmForm::setAddress))
                .addMappings(mapping -> mapping.skip(ConfirmForm::setTel))
                .addMappings(mapping -> mapping.skip(ConfirmForm::setEmail))
                .addMappings(mapping -> mapping.skip(ConfirmForm::setType1))
                .addMappings(mapping -> mapping.skip(ConfirmForm::setType2))
                .addMappings(mapping -> mapping.skip(ConfirmForm::setInquiry))
                .addMappings(mapping -> mapping.skip(ConfirmForm::setSurvey));
    }

    /**
     * 文字列を指定された区切り文字で結合する。文字列が全て空の場合には空文字列を返す。
     *
     * @param delimiter 区切り文字
     * @param arg       結合する文字列の配列
     * @return 結合した文字列
     */
    private String join(String delimiter, String... arg) {
        boolean isAllEmpty = arg == null
                || Arrays.asList(arg).stream().allMatch(str -> StringUtils.isEmpty(str));
        return isAllEmpty
                ? StringUtils.EMPTY
                : Arrays.asList(arg).stream().collect(Collectors.joining(delimiter));
    }

}

InquiryConfirmController クラスを変更する

src/main/java/ksbysample/webapp/bootnpmgeb/web/inquiry/InquiryConfirmController.java の以下の点を変更します。

@Controller
@RequestMapping("/inquiry/confirm")
@SessionAttributes("sessionData")
public class InquiryConfirmController {

    private static final String TEMPLATE_BASE = "web/inquiry";
    private static final String TEMPLATE_CONFIRM = TEMPLATE_BASE + "/confirm";

    private final ModelMapper modelMapper;

    /**
     * コンストラクタ
     *
     * @param modelMapper {@ModelMapper} オブジェクト
     */
    public InquiryConfirmController(ModelMapper modelMapper) {
        this.modelMapper = modelMapper;
    }

    /**
     * 確認画面 初期表示処理
     *
     * @param confirmForm {@ConfirmForm} オブジェクト
     * @param sessionData {@SessionData} オブジェクト
     * @return 確認画面の Thymeleaf テンプレートファイルのパス
     */
    @GetMapping
    public String index(ConfirmForm confirmForm
            , SessionData sessionData) {
        modelMapper.map(sessionData, confirmForm);
        return TEMPLATE_CONFIRM;
    }

    ..........

}
  • private final ModelMapper modelMapper; を追加します。
  • コンストラクタを追加します。
  • index メソッドに ConfirmForm confirmForm, SessionData sessionData の引数を追加し、modelMapper.map(sessionData, confirmForm); を追加します。

confirm.html を変更する

src/main/resources/templates/web/inquiry/confirm.html の以下の点を変更します。

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head th:replace="~{web/common/fragments :: common_header(~{::title}, ~{::link}, ~{::style})}">
  <title>入力フォーム - 確認画面</title>

  <style>
    /* セルの上部の罫線を表示しないようようにし、セル内の余白を詰める */
    .table > tbody > tr > th,
    .table > tbody > tr > td {
      border-top: none;
      padding: 5px;
    }
  </style>
</head>

<body class="skin-blue layout-top-nav">
<div class="wrapper">

  <!-- Content Wrapper. Contains page content -->
  <div class="content-wrapper">
    <!-- Content Header (Page header) -->
    <section class="content-header">
      <h1>
        確認画面
      </h1>
    </section>

    <!-- Main content -->
    <section class="content">
      <div class="row">
        <div class="col-xs-12">
          <!--/*@thymesVar id="confirmForm" type="ksbysample.webapp.bootnpmgeb.web.inquiry.form.ConfirmForm"*/-->
          <form id="confirmForm" method="post" action=""
                th:action="@{/inquiry/confirm/}"
                th:object="${confirmForm}">
            <table class="table">
              <colgroup>
                <col width="15%"/>
                <col width="85%"/>
              </colgroup>

              <!-- 入力画面1の項目 -->
              <tr>
                <th nowrap>お名前(漢字)</th>
                <td th:text="*{name}">田中 太郎</td>
              </tr>
              <tr>
                <th nowrap>お名前(かな)</th>
                <td th:text="*{kana}">たなか たろう</td>
              </tr>
              <tr>
                <th nowrap>性別</th>
                <td th:text="*{sex}">男性</td>
              </tr>
              <tr>
                <th nowrap>年齢</th>
                <td>
                  <th:block th:text="*{age}">30</th:block></td>
              </tr>
              <tr>
                <th nowrap>職業</th>
                <td th:text="*{job}">会社員</td>
              </tr>
              <tr>
                <td colspan="2">
                  <button class="btn bg-blue js-btn-input01"><i class="fa fa-arrow-left"></i> 修正する</button>
                </td>
              </tr>

              <!-- 入力画面2の項目 -->
              <tr>
                <th nowrap>郵便番号</th>
                <td><th:block th:text="*{zipcode}">102-0072</th:block>
                </td>
              </tr>
              <tr>
                <th nowrap>住所</th>
                <td th:text="*{address}">東京都千代田区飯田橋1-1</td>
              </tr>
              <tr>
                <th nowrap>電話番号</th>
                <td th:text="*{tel}">03-1234-5678</td>
              </tr>
              <tr>
                <th nowrap>メールアドレス</th>
                <td th:text="*{email}">taro.tanaka@sample.co.jp</td>
              </tr>
              <tr>
                <td colspan="2">
                  <button class="btn bg-blue js-btn-input02"><i class="fa fa-arrow-left"></i> 修正する</button>
                </td>
              </tr>

              <!-- 入力画面3の項目 -->
              <tr>
                <th nowrap>お問い合わせの種類1</th>
                <td th:text="*{type1}">製品に関するお問い合わせ</td>
              </tr>
              <tr>
                <th nowrap>お問い合わせの種類2</th>
                <td th:text="*{type2}">見積が欲しい</td>
              </tr>
              <tr>
                <th nowrap>お問い合わせの内容</th>
                <td th:utext="*{inquiry} ? ${#strings.replace(#strings.escapeXml(confirmForm.inquiry), T(java.lang.System).getProperty('line.separator'), '&lt;br /&gt;')} : ''">
                  ここに、<br/>
                  入力されたお問い合わせの内容が表示されます。
                </td>
              </tr>
              <tr>
                <th nowrap>アンケート</th>
                <td>
                  <ul style="padding-left: 20px"
                      th:each="sv : *{survey}">
                    <li th:text="${sv}">選択肢1だけ長くしてみる</li>
                  </ul>
                </td>
              </tr>
              <tr>
                <td colspan="2">
                  <button class="btn bg-blue js-btn-input03"><i class="fa fa-arrow-left"></i> 修正する</button>
                </td>
              </tr>
            </table>

            <div class="text-center">
              <button class="btn bg-green js-btn-send"><i class="fa fa-arrow-right"></i> 送信する</button>
            </div>
          </form>
        </div>
      </div>
    </section>
    <!-- /.content -->
  </div>
  <!-- /.content-wrapper -->
</div>
<!-- ./wrapper -->

<!-- REQUIRED JS SCRIPTS -->
<script src="/js/inquiry/confirm.js"></script>

</body>
</html>
  • form タグの上に <!--/*@thymesVar id="confirmForm" type="ksbysample.webapp.bootnpmgeb.web.inquiry.form.ConfirmForm"*/--> を追加します。
  • form タグの末尾に th:object="${confirmForm}" を追加します。
  • 各表示項目を以下のように変更します。
    • 基本的には td タグに th:text="*{...}"(... には入力項目に対応した変数を記述) を追加します。
    • textarea の入力項目である「お問い合わせの内容」には th:utext="*{inquiry} ? ${#strings.replace(#strings.escapeXml(confirmForm.inquiry), T(java.lang.System).getProperty('line.separator'), '&lt;br /&gt;')} : ''" を追加します。改行は <br/> に変換して出力します。
    • 「アンケート」はリストを出力するので ul タグに th:each="sv : *{survey}" を、li タグに th:text="${sv}" を追加します。

動作確認

動作確認します。npm run springboot コマンドを実行し Tomcat を起動した後、ブラウザで http://localhost:9080/inquiry/input/01/ にアクセスします。

入力画面1~3に以下の画像のデータを入力します。

f:id:ksby:20180613211511p:plain f:id:ksby:20180613211616p:plain f:id:ksby:20180613211723p:plain

確認画面を表示すると入力したデータが表示されます。

f:id:ksby:20180613211957p:plain f:id:ksby:20180613212129p:plain

  • ラジオボタンやドロップダウンリストで選択する項目は、選択された文字列が表示されています。
  • 「郵便番号」や「電話番号」は入力した値が "-" で結合して表示されています。
  • 「お問い合わせの種類2」は選択した項目が "、" で結合して表示されています。
  • 「お問い合わせの内容」は改行した箇所は改行されて表示されています。
  • 「アンケート」は選択した項目が列挙されています。

「職業」や「アンケート」を選択しなかったり、「電話番号」を入力しないと、

f:id:ksby:20180613232244p:plain f:id:ksby:20180613232332p:plain f:id:ksby:20180613232429p:plain

確認画面の「職業」「電話番号」「アンケート」には何も表示されません。

f:id:ksby:20180613232624p:plain

次は。。。

「修正する」ボタンの処理→「送信する」ボタンの処理の順に実装します。

履歴

2018/06/13
初版発行。

Spring Boot + npm + Geb で入力フォームを作ってテストする ( その57 )( build.gradle に記述する BOM を Spring IO Platform のものから Spring Boot のものに変更する )

概要

記事一覧はこちらです。

Spring Boot + npm + Geb で入力フォームを作ってテストする ( その56 )( PMD を 5.8.1 → 6.4.0 へバージョンアップする2 ) の続きです。

  • 今回の手順で確認できるのは以下の内容です。
    • build.gradle に記述する BOM は Spring IO Platform のものを利用していたのですが、9 April 2019 に EOL となることが決まったので Spring Boot の BOM を利用するように変更します。

参照したサイト・書籍

目次

  1. build.gradle を変更する
  2. build.gradle の plugin の記述を plugins { id "..." } の書式に変更する

手順

build.gradle を変更する

67. Spring Boot Gradle plugin に記載されているように Spring Boot Gradle plugin を使用していれば spring-boot-starter-parent の BOM が自動で適用されるのですが、個人的な好みで BOM は明記することにします。

build.gradle の以下の点を変更します。

dependencyManagement {
    imports {
        // mavenBom は以下の URL のものを使用する
        // https://repo.spring.io/release/org/springframework/boot/spring-boot-starter-parent/1.5.10.RELEASE/
        // bomProperty に指定可能な property は以下の URL の BOM に記述がある
        // https://repo.spring.io/release/org/springframework/boot/spring-boot-dependencies/1.5.10.RELEASE/spring-boot-dependencies-1.5.10.RELEASE.pom
        mavenBom("org.springframework.boot:spring-boot-starter-parent:${springBootVersion}") {
            bomProperty 'thymeleaf.version', '3.0.9.RELEASE'
            bomProperty 'thymeleaf-extras-springsecurity4.version', '3.0.2.RELEASE'
            bomProperty 'thymeleaf-layout-dialect.version', '2.2.2'
            bomProperty 'thymeleaf-extras-data-attribute.version', '2.0.1'
            bomProperty 'thymeleaf-extras-java8time.version', '3.0.1.RELEASE'
        }
    }
}

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

    // dependency-management-plugin によりバージョン番号が自動で設定されないもの、あるいは最新バージョンを指定したいもの
    ..........
    compile("com.google.guava:guava:22.0")
    compile("org.apache.commons:commons-lang3:3.7")
    ..........
}
  • mavenBom("io.spring.platform:platform-bom:Brussels-SR7")mavenBom("org.springframework.boot:spring-boot-starter-parent:${springBootVersion}") に変更します。
  • bomProperty 'guava.version', '22.0' を削除します。
  • Spring Boot の BOM では guava と commons-lang3 はサポートされていないので、記述する位置を変更し、バージョンを明記します。
    • compile("com.google.guava:guava")compile("com.google.guava:guava:22.0")
    • compile("org.apache.commons:commons-lang3")compile("org.apache.commons:commons-lang3:3.7")

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

モジュールのバージョンを確認します。Spring Boot は 1.5.10.RELEASE が使用されており、

f:id:ksby:20180609075539p:plain

Thymeleaf も 3.0.9.RELEASE が使用されています。

f:id:ksby:20180609075711p:plain

clean タスク実行 → Rebuild Project 実行 → build タスクを実行します。1件だけある必ず失敗するテストをコメントアウトせずに build タスクを実行したので test タスクはエラーが出ましたが、それ以外は問題なさそうです。また BOM を変更したことで追加でダウンロードされたモジュールがありました。

f:id:ksby:20180609082046p:plain

guava-23.5-jre.pom がダウンロードされていたので、プロジェクトで使用する guava も build.gradle で指定している 22.0 でないのか?と思い gradlew dependencies コマンドを実行して確認したところ、23 を使用しているのは checkstyle で、Web アプリの方では 22.0 が使用されていました。

checkstyle - The Checkstyle libraries to be used for this project.
\--- com.puppycrawl.tools:checkstyle:8.8
     +--- antlr:antlr:2.7.7
     +--- org.antlr:antlr4-runtime:4.7.1
     +--- commons-beanutils:commons-beanutils:1.9.3
     |    \--- commons-collections:commons-collections:3.2.2
     +--- commons-cli:commons-cli:1.4
     +--- com.google.guava:guava:23.6-jre
     |    +--- com.google.code.findbugs:jsr305:1.3.9
     |    +--- org.checkerframework:checker-compat-qual:2.0.0
     |    +--- com.google.errorprone:error_prone_annotations:2.1.3
     |    +--- com.google.j2objc:j2objc-annotations:1.1
     |    \--- org.codehaus.mojo:animal-sniffer-annotations:1.14
     \--- net.sf.saxon:Saxon-HE:9.8.0-7

compile - Dependencies for source set 'main' (deprecated, use 'implementation ' instead).
+--- org.springframework.boot:spring-boot-starter-web -> 1.5.10.RELEASE
..........
+--- com.google.guava:guava:22.0
|    +--- com.google.code.findbugs:jsr305:1.3.9
|    +--- com.google.errorprone:error_prone_annotations:2.0.18
|    +--- com.google.j2objc:j2objc-annotations:1.1
|    \--- org.codehaus.mojo:animal-sniffer-annotations:1.14
+--- org.apache.commons:commons-lang3:3.7
\--- org.seasar.doma:doma:2.19.1

build.gradle の plugin の記述を plugins { id '...' } の書式に変更する

今回の変更で 67. Spring Boot Gradle plugin を見て、今の build.gradle の plugin の書き方が古いことに気づいたので、新しい書き方に変更します。

build.gradle の以下の点を変更します。

buildscript {
    ext {
        group "ksbysample"
        version "1.0.1-RELEASE"
        springBootVersion = "1.5.10.RELEASE"
    }
}

plugins {
    id "java"
    id "eclipse"
    id "idea"
    // plugins {} block 内では ${springBootVersion} が使用できないので、バージョンを直接記述している
    id "org.springframework.boot" version "1.5.10.RELEASE"
    id "groovy"
    id "net.ltgt.errorprone" version "0.0.13"
    id "checkstyle"
    id "findbugs"
    id "pmd"
    id "com.moowork.node" version "1.2.0"
}

sourceCompatibility = 1.8
targetCompatibility = 1.8

..........
  • buildscript { ... } の前に group ...version ... を記述していると only buildscript {} and other plugins {} script blocks are allowed before plugins {} blocks, no other statements are allowed というエラーメッセージが出力されますので、以下の2行を buildscript { ext { ... } } の中に移動します。
    • group 'ksbysample'
    • version '1.0.0-RELEASE'
  • buildscript { ... } 内の repositories { ... }dependencies { ... } を削除します。
  • apply plugin: '...' の記述全てを plugins { ... } で囲み、apply plugin:id に変更します。
  • 以下の3つのプラグインversion ... を追加します。
    • id "org.springframework.boot"
    • id "net.ltgt.errorprone"
    • id "com.moowork.node"

id "org.springframework.boot" では version ${springBootVersion} と記述したかったのですが、Gradle Tool Window の左上にある「Refresh all Gradle projects」ボタンをクリックして更新するとエラーが出ます。Allow the plugin DSL to expand properties as part of the version によるとまだ変数は使用できないようです。今は直接記述するようにします。

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

clean タスク実行 → Rebuild Project 実行 → build タスクを実行して BUILD SUCCESSFUL が出力されることを確認します(失敗するテストはコメントアウトしてあります)。

f:id:ksby:20180610193339p:plain

履歴

2018/06/10
初版発行。

Spring Boot + npm + Geb で入力フォームを作ってテストする ( その56 )( PMD を 5.8.1 → 6.4.0 へバージョンアップする2 )

概要

記事一覧はこちらです。

Spring Boot + npm + Geb で入力フォームを作ってテストする ( その55 )( PMD を 5.8.1 → 6.4.0 へバージョンアップする ) の続きです。

  • 今回の手順で確認できるのは以下の内容です。
    • PMD を 5.8.1 → 6.4.0 へバージョンアップします。前回からの続きです。

参照したサイト・書籍

目次

  1. category/java/documentation.xml
    1. Field comments are required
    2. Document empty method body
    3. Comment is too large: Line too long
    4. Enum comments are required
  2. category/java/errorprone.xml
    1. Found '...'-anomaly for variable '...' (lines ...).
    2. Found non-transient, non-static member. Please mark as transient or provide accessors.
    3. Avoid using Literals in Conditional Statements
    4. Class cannot be instantiated and does not provide any static methods or fields
  3. category/java/multithreading.xml
  4. category/java/performance.xml
    1. StringBuffer (or StringBuilder).append is called consecutively without reusing the target variable.
    2. Avoid using redundant field initializer for '...'
  5. category/java/security.xml
  6. config/pmd/pmd-project-rulesets.xml はこうなりました

手順

category/java/documentation.xml

設定を追加して build タスクを実行すると 112 PMD rule violations were found. と出力されました。メッセージは以下の4種類でした。

  • Field comments are required
  • Document empty method body
  • Comment is too large: Line too long
  • Enum comments are required

Field comments are required

CommentRequired のルール違反のメッセージです。前は <property name="fieldCommentRequirement" value="Ignored"/> と設定していたので、今回も同じ設定を入れます。

Document empty method body

UncommentedEmptyMethodBody のルール違反のメッセージです。前も exclude していたので、今回も exclude します。

Comment is too large: Line too long

CommentSize のルール違反のメッセージです。

このルールでは1行の最大文字数が 80 文字に設定されていますが IntelliJ IDEA では1行の最大文字数は default では 120 なので、<property name="maxLineLength" value="120"/> を入れて IntelliJ IDEA と同じ文字数になるようにします。

Enum comments are required

これも CommentRequired のルール違反のメッセージです。前は <property name="enumCommentRequirement" value="Ignored"/> と設定していたので、今回も同じ設定を入れます。

以上で category/java/documentation.xml は以下のような定義になりました。

    <rule ref="category/java/documentation.xml">
        <!-- CommentRequired はここでは exclude し、下で別途定義する -->
        <exclude name="CommentRequired"/>
        <exclude name="UncommentedEmptyMethodBody"/>
        <!-- CommentSize はここでは exclude し、下で別途定義する -->
        <exclude name="CommentSize"/>
    </rule>
    <rule ref="category/java/documentation.xml/CommentRequired">
        <properties>
            <property name="fieldCommentRequirement" value="Ignored"/>
            <property name="enumCommentRequirement" value="Ignored"/>
        </properties>
    </rule>
    <rule ref="category/java/documentation.xml/CommentSize">
        <properties>
            <property name="maxLineLength" value="120"/>
        </properties>
    </rule>

category/java/errorprone.xml

設定を追加して build タスクを実行すると 30 PMD rule violations were found. と出力されました。メッセージは以下の4種類でした。

  • Found '...'-anomaly for variable '...' (lines ...).
  • Found non-transient, non-static member. Please mark as transient or provide accessors.
  • Avoid using Literals in Conditional Statements
  • Class cannot be instantiated and does not provide any static methods or fields

Found '...'-anomaly for variable '...' (lines ...).

DataflowAnomalyAnalysis のルール違反のメッセージです。

メッセージが出た箇所を見ると lambda 式の引数で出ているようで、対応のしようがないので exclude します。lambda 式でルール違反のメッセージが出るようでは、このルールは使えないのでは?

Found non-transient, non-static member. Please mark as transient or provide accessors.

BeanMembersShouldSerialize のルール違反のメッセージです。

メッセージが出た箇所を見ると DI 用のフィールド変数でした。対応のしようがないので exclude します。

Avoid using Literals in Conditional Statements

AvoidLiteralsInIfCondition のルール違反のメッセージです。

条件式で定数を直接記述していると出るメッセージでした。定数文字列を定義してそれを使用するようソースを修正します。

Class cannot be instantiated and does not provide any static methods or fields

MissingStaticMethodInNonInstantiatableClass のルール違反のメッセージです。

リンク先の説明を読みましたが、今は必要そうに思えなかったので exclude することにします。

以上で category/java/errorprone.xml は以下のような定義になりました。

    <rule ref="category/java/errorprone.xml">
        <exclude name="DataflowAnomalyAnalysis"/>
        <exclude name="BeanMembersShouldSerialize"/>
        <exclude name="MissingStaticMethodInNonInstantiatableClass"/>
    </rule>

category/java/multithreading.xml

設定を追加して build タスクを実行しても PMD のルール違反は出ませんでした。

category/java/performance.xml

設定を追加して build タスクを実行すると 11 PMD rule violations were found. と出力されました。メッセージは以下の2種類でした。

  • StringBuffer (or StringBuilder).append is called consecutively without reusing the target variable.
  • Avoid using redundant field initializer for '...'

StringBuffer (or StringBuilder).append is called consecutively without reusing the target variable.

ConsecutiveAppendsShouldReuse のルール違反のメッセージです。

指摘された箇所を

        sb.append("ControllerName = ");
        sb.append(loggingGamenName);
        sb.append(", ");

以下のように修正します。

        sb.append("ControllerName = ")
                .append(loggingGamenName)
                .append(", ");

Avoid using redundant field initializer for '...'

RedundantFieldInitializer のルール違反のメッセージです。

リンク先を読むとフィールド変数をデフォルト値と同じ値で変数を初期化しているとメッセージが表示されるルールでした。確かにメッセージが出ている箇所を確認したところ boolean 型のフィールド変数を false で初期化していました。

メッセージが出ているフィールド変数に false をセットしている処理を削除することにします。

category/java/security.xml

設定を追加して build タスクを実行しても PMD のルール違反は出ませんでした。

config/pmd/pmd-project-rulesets.xml はこうなりました

最終版の config/pmd/pmd-project-rulesets.xml を記載しておきます。最初はバージョンアップ後に大量にメッセージが出力されたので移行は大変かと思いましたが、Rule が8種類のCategory に分類されたことと、独自 Rulesets の書き方さえ分かれば大したことはありませんでした。

<?xml version="1.0" encoding="UTF-8"?>
<ruleset name="mybraces"
         xmlns="http://pmd.sourceforge.net/ruleset/2.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 http://pmd.sourceforge.net/ruleset_2_0_0.xsd">
    <description>project rulesets</description>

    <!--
        rulesets の種類・説明は 以下の URL 参照
        https://github.com/pmd/pmd/tree/master/pmd-java/src/main/resources/category/java
        https://github.com/pmd/pmd/tree/master/pmd-java/src/main/resources/rulesets/java
        https://pmd.github.io/pmd-6.4.0/pmd_rules_java.html
        ※"pmd-6.4.0" の部分は適用しているバージョンに変更すること。
    -->
    <rule ref="category/java/bestpractices.xml"/>
    <rule ref="category/java/codestyle.xml">
        <exclude name="VariableNamingConventions"/>
        <exclude name="MethodArgumentCouldBeFinal"/>
        <exclude name="LongVariable"/>
        <exclude name="LocalVariableCouldBeFinal"/>
        <exclude name="AtLeastOneConstructor"/>
        <exclude name="ShortVariable"/>
        <exclude name="UnnecessaryAnnotationValueElement"/>
        <exclude name="ClassNamingConventions"/>
        <exclude name="DefaultPackage"/>
        <exclude name="CommentDefaultAccessModifier"/>
        <exclude name="OnlyOneReturn"/>
    </rule>
    <rule ref="category/java/design.xml">
        <exclude name="LoosePackageCoupling"/>
        <exclude name="UseUtilityClass"/>
        <exclude name="NcssCount"/>
        <exclude name="LawOfDemeter"/>
        <exclude name="DataClass"/>
        <exclude name="UseObjectForClearerAPI"/>
        <exclude name="CyclomaticComplexity"/>
    </rule>
    <rule ref="category/java/documentation.xml">
        <!-- CommentRequired はここでは exclude し、下で別途定義する -->
        <exclude name="CommentRequired"/>
        <exclude name="UncommentedEmptyMethodBody"/>
        <!-- CommentSize はここでは exclude し、下で別途定義する -->
        <exclude name="CommentSize"/>
    </rule>
    <rule ref="category/java/documentation.xml/CommentRequired">
        <properties>
            <property name="fieldCommentRequirement" value="Ignored"/>
            <property name="enumCommentRequirement" value="Ignored"/>
        </properties>
    </rule>
    <rule ref="category/java/documentation.xml/CommentSize">
        <properties>
            <property name="maxLineLength" value="120"/>
        </properties>
    </rule>
    <rule ref="category/java/errorprone.xml">
        <exclude name="DataflowAnomalyAnalysis"/>
        <exclude name="BeanMembersShouldSerialize"/>
        <exclude name="MissingStaticMethodInNonInstantiatableClass"/>
    </rule>
    <rule ref="category/java/multithreading.xml"/>
    <rule ref="category/java/performance.xml"/>
    <rule ref="category/java/security.xml"/>
</ruleset>

ちなみに clean タスク実行 → Rebuild Project 実行 → build タスクを実行するとこうなります。

f:id:ksby:20180603235112p:plain

履歴

2018/06/04
初版発行。

Spring Boot + npm + Geb で入力フォームを作ってテストする ( その55 )( PMD を 5.8.1 → 6.4.0 へバージョンアップする )

概要

記事一覧はこちらです。

Spring Boot + npm + Geb で入力フォームを作ってテストする ( その54 )( webpack を 3.8.1 → 4.9.1 へバージョンアップする ) の続きです。

  • 今回の手順で確認できるのは以下の内容です。
    • PMD を 5.8.1 → 6.4.0 へバージョンアップします。2~3回に分ける予定です。
    • PMD 6.0.0 から Java 9 に対応しており(6.4.0 で Java 10 に対応)、Rule が8種類の Category に分類されて独自の Rulesets の書き方も変わっています。

参照したサイト・書籍

目次

  1. build.gradle を変更する
  2. 変更の方針を決める
  3. category/java/bestpractices.xml
    1. Unsupported build listener: class org.gradle.api.internal.project.ant.AntLoggingAdapterUnsupported build listener: class org.apache.tools.ant.AntClassLoader
    2. This analysis could be faster, please consider using Incremental Analysis: https://pmd.github.io/pmd-6.4.0/pmd_userdocs_getting_started.html#incremental-analysis
  4. category/java/codestyle.xml
    1. Variables that are final and static should be all capitals, '...' is not all capitals.
    2. Parameter '...' is not assigned and could be declared final
    3. Avoid excessively long variable names like ...
    4. Local variable '...' could be declared final
    5. Each class should declare at least one constructor
    6. Avoid variables with short names like ...
    7. Avoid the use of value in annotations when its the only element
    8. Avoid unnecessary constructors - the compiler will generate these for you
    9. The utility class name '...' doesn't match '[A-Z][a-zA-Z0-9]+(Utils?|Helper)'
    10. Use explicit scoping instead of the default package private level
    11. To avoid mistakes add a comment at the beginning of the ... field if you want a default access modifier
    12. A method should have only one exit point, and that should be the last statement in the method
  5. category/java/design.xml
    1. Removed misconfigured rule: LoosePackageCoupling cause: No packages or classes specified
    2. All methods are static. Consider using a utility class instead. Alternatively, you could add a private constructor or make the class abstract to silence this warning.
    3. The method '...' has a NCSS line count of ....
    4. Potential violation of Law of Demeter (method chain calls)
    5. This class has too many methods, consider refactoring it.
    6. The class '...' is suspected to be a Data Class
    7. Avoid throwing raw exception types.
    8. Rather than using a lot of String arguments, consider using a container object for those values.
    9. The method '...' has a cyclomatic complexity of ....
  6. ここまでやってみた感想

手順

build.gradle を変更する

build.gradle の以下の点を変更します。

pmd {
    toolVersion = "6.4.0"
    sourceSets = [project.sourceSets.main]
    ignoreFailures = true
    consoleOutput = true
    ruleSetFiles = rootProject.files("/config/pmd/pmd-project-rulesets.xml")
    ruleSets = []
}
  • toolVersion = "5.8.1"toolVersion = "6.4.0" に変更します。

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

clean タスク実行 → Rebuild Project 実行 → build タスクを実行してみますが、相変わらず大量にエラーが出ています。。。

f:id:ksby:20180602142545p:plain

変更の方針を決める

PMD Release Notes - 15-December-2017 - 6.0.0PMD Making RulesetsJava Rules を見ると、Rule の Category が一新されていて1から見直した方が良さそうに思えたので、以下の方針で変更します。

  • pmd-project-rulesets.xml は1から作り直します。build タスク実行で出力されたエラーメッセージを元に修正はしません。
  • pmd-project-rulesets.xml には <rule ref="category/java/bestpractices.xml" /> のフォーマットで Category の xml ファイルを1つずつ記述して、build タスクを実行して結果を見て調整します。
  • 現在 exclude している Rule は、基本的に同じように exclude します。

config/pmd/pmd-project-rulesets.xml を以下の内容に書き換えます。

<?xml version="1.0" encoding="UTF-8"?>
<ruleset name="mybraces"
         xmlns="http://pmd.sourceforge.net/ruleset/2.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 http://pmd.sourceforge.net/ruleset_2_0_0.xsd">
    <description>project rulesets</description>

    <!--
        rulesets の種類・説明は 以下の URL 参照
        https://github.com/pmd/pmd/tree/master/pmd-java/src/main/resources/category/java
        https://github.com/pmd/pmd/tree/master/pmd-java/src/main/resources/rulesets/java
        https://pmd.github.io/pmd-6.4.0/pmd_rules_java.html
        ※"pmd-6.4.0" の部分は適用しているバージョンに変更すること。
    -->
    <rule ref="category/java/bestpractices.xml"/>
    ..........
</ruleset>

<rule ref="category/java/bestpractices.xml"/> の部分に1つずつ category を記述して調整していきます。

category/java/bestpractices.xml

PMD のルール違反は出ませんでしたが、それ以外で以下のメッセージが出力されました。

:pmdMain
Unsupported build listener: class org.gradle.api.internal.project.ant.AntLoggingAdapter
Unsupported build listener: class org.apache.tools.ant.AntClassLoader
This analysis could be faster, please consider using Incremental Analysis: https://pmd.github.io/pmd-6.4.0/pmd_userdocs_getting_started.html#incremental-analysis
This analysis could be faster, please consider using Incremental Analysis: https://pmd.github.io/pmd-6.4.0/pmd_userdocs_getting_started.html#incremental-analysis

Unsupported build listener: class org.gradle.api.internal.project.ant.AntLoggingAdapterUnsupported build listener: class org.apache.tools.ant.AntClassLoader

コマンドラインから gradlew cleangradlew --debug pmdMain を実行してみたところ、以下のログが出力されました。

16:06:18.934 [WARN] [org.gradle.api.internal.project.ant.AntLoggingAdapter] Unsupported build listener: class org.gradle.api.internal.project.ant.AntLoggingAdapter
16:06:18.935 [WARN] [org.gradle.api.internal.project.ant.AntLoggingAdapter] Unsupported build listener: class org.apache.tools.ant.AntClassLoader

Unsupported build listener で調べてみると Gradle - Method details - addBuildListener) から Interface BuildListener が見つかりました。AntLoggingAdapter と AntClassLoader のクラスで BuildListener インターフェースが実装されていないのが原因のようです。

WARN のログなので、出力されたままにすることにします。

This analysis could be faster, please consider using Incremental Analysis: https://pmd.github.io/pmd-6.4.0/pmd_userdocs_getting_started.html#incremental-analysis

https://pmd.github.io/pmd-6.4.0/pmd_userdocs_getting_started.html#incremental-analysis のリンク先のページを見ると、なぜか Gradle だけリンクになっていませんね。。。

他にも調べてみると Revert "Support PMD's analysis cache (#2223)" が見つかりました。おそらく原因はこれだと思うのですが、まだ解決していないようです。対策がないようですので、出力されたままにします。

category/java/codestyle.xml

設定を追加して build タスクを実行すると 307 PMD rule violations were found. と出力されました。メッセージは以下の12種類でした。

  • Variables that are final and static should be all capitals, '...' is not all capitals.
  • Parameter '...' is not assigned and could be declared final
  • Avoid excessively long variable names like ...
  • Local variable '...' could be declared final
  • Each class should declare at least one constructor
  • Avoid variables with short names like ...
  • Avoid the use of value in annotations when its the only element
  • Avoid unnecessary constructors - the compiler will generate these for you
  • The utility class name '...' doesn't match '[A-Z][a-zA-Z0-9]+(Utils?|Helper)'
  • Use explicit scoping instead of the default package private level
  • To avoid mistakes add a comment at the beginning of the ... field if you want a default access modifier
  • A method should have only one exit point, and that should be the last statement in the method

Variables that are final and static should be all capitals, '...' is not all capitals.

VariableNamingConventions のルール違反のメッセージです。前も exclude していたので、今回も exclude します。

Parameter '...' is not assigned and could be declared final

MethodArgumentCouldBeFinal のルール違反のメッセージです。前も exclude していたので、今回も exclude します。

Avoid excessively long variable names like ...

LongVariable のルール違反のメッセージです。前も exclude していたので、今回も exclude します。

Local variable '...' could be declared final

LocalVariableCouldBeFinal のルール違反のメッセージです。前も exclude していたので、今回も exclude します。

Each class should declare at least one constructor

AtLeastOneConstructor のルール違反のメッセージです。前は何もしていませんでしたが、今のところ対応する必要性を感じなかったので exclude します。

Avoid variables with short names like ...

ShortVariable のルール違反のメッセージです。前も exclude していたので、今回も exclude します。

Avoid the use of value in annotations when its the only element

UnnecessaryAnnotationValueElement のルール違反のメッセージです。PMD 6.2.0 から新規に追加された Rule です。指摘された箇所のアノテーションは削除しようがなかったので、exclude します。

Avoid unnecessary constructors - the compiler will generate these for you

UnnecessaryConstructor のルール違反のメッセージです。

メッセージが出ているのは src/main/java/ksbysample/webapp/bootnpmgeb/config/DomaConfig.java で、ルール違反を指摘されているコンストラクタを削除しても特に問題なかったので、ソースを修正してメッセージが出ないようにします。

The utility class name '...' doesn't match '[A-Z][a-zA-Z0-9]+(Utils?|Helper)'

ClassNamingConventions のルール違反のメッセージです。

メッセージが出ているのは以下の2つのクラスですが、static のフィールド・メソッドしかないと出ているようです。

  • src/main/java/ksbysample/webapp/bootnpmgeb/constants/UrlConst.java
  • src/main/java/ksbysample/webapp/bootnpmgeb/util/validator/EmailValidator.java

個人的にクラス名を変更したくなかったので、exclude することにします。

Use explicit scoping instead of the default package private level

DefaultPackage のルール違反のメッセージです。

メッセージが出ているのは Doma 2 の Entity クラスで対応しようがなかったので、exclude することにします。

To avoid mistakes add a comment at the beginning of the ... field if you want a default access modifier

CommentDefaultAccessModifier のルール違反のメッセージです。

メッセージが出ているのは Doma 2 の Entity クラスで、DomaGen で生成した時にコメントがついていないのが原因なので(確か COMMENT ON COLUMN ... IS '...'; でカラムにコメントを付けるとコメントに出るはず)、exclude することにします。

A method should have only one exit point, and that should be the last statement in the method

OnlyOneReturn のルール違反のメッセージです。

個人的にはメソッド内に複数 return を入れても構わないと思っているので、exclude します。

以上で category/java/codestyle.xml は以下のような定義になりました。

    <rule ref="category/java/codestyle.xml">
        <exclude name="VariableNamingConventions"/>
        <exclude name="MethodArgumentCouldBeFinal"/>
        <exclude name="LongVariable"/>
        <exclude name="LocalVariableCouldBeFinal"/>
        <exclude name="AtLeastOneConstructor"/>
        <exclude name="ShortVariable"/>
        <exclude name="UnnecessaryAnnotationValueElement"/>
        <exclude name="ClassNamingConventions"/>
        <exclude name="DefaultPackage"/>
        <exclude name="CommentDefaultAccessModifier"/>
        <exclude name="OnlyOneReturn"/>
    </rule>

category/java/design.xml

設定を追加して build タスクを実行すると 95 PMD rule violations were found. と出力されました。メッセージは以下の9種類でした。

  • Removed misconfigured rule: LoosePackageCoupling cause: No packages or classes specified
  • All methods are static. Consider using a utility class instead. Alternatively, you could add a private constructor or make the class abstract to silence this warning.
  • The method '...' has a NCSS line count of ....
  • Potential violation of Law of Demeter (method chain calls)
  • This class has too many methods, consider refactoring it.
  • The class '...' is suspected to be a Data Class
  • Avoid throwing raw exception types.
  • Rather than using a lot of String arguments, consider using a container object for those values.
  • The method '...' has a cyclomatic complexity of ....

Removed misconfigured rule: LoosePackageCoupling cause: No packages or classes specified

LoosePackageCoupling の設定がないというメッセージのようです。

リンク先の説明を読みましたが、今は必要そうに思えなかったので exclude することにします。

All methods are static. Consider using a utility class instead. Alternatively, you could add a private constructor or make the class abstract to silence this warning.

UseUtilityClass のルール違反のメッセージです。

ClassNamingConventions を exclude したのと同じ理由で、こちらも exclude します。

The method '...' has a NCSS line count of ....

NcssCount のルール違反のメッセージです。PMD 6.0.0 から新規に追加された Rule です。

実はこのルールが何を指摘しているのかよく分かりませんでした。。。 対応方法もよく分からないので exclude します。

Potential violation of Law of Demeter (method chain calls)

LawOfDemeter のルール違反のメッセージです。

リンク先の説明を読みましたが、有効性がよく分からなかったので exclude します。

This class has too many methods, consider refactoring it.

TooManyMethods のルール違反のメッセージです。

このルールは残しておきたいので、メッセージが表示された以下のクラスに @SuppressWarnings("PMD.TooManyMethods") を付けることにします。

  • src/main/java/ksbysample/webapp/bootnpmgeb/aspect/logging/RequestAndResponseLogger.java

The class '...' is suspected to be a Data Class

DataClass のルール違反のメッセージです。PMD 6.0.0 から新規に追加された Rule です。

メッセージが出ていたのが Doma 2 の Entity クラスだったのですが対応のしようがないのと、このルールの必要性も感じなかったので、exclude します。

Avoid throwing raw exception types.

AvoidThrowingRawExceptionTypes のルール違反のメッセージです。

エラーが明確に分かる例外クラスではなく RuntimeException を throw していたところでメッセージが出ていました。確かにその通りなので、今回はメッセージが出ている以下のクラスに @SuppressWarnings("PMD.AvoidThrowingRawExceptionTypes") を付けることにします。

  • src/main/java/ksbysample/webapp/bootnpmgeb/helper/freemarker/FreeMarkerHelper.java
  • src/main/java/ksbysample/webapp/bootnpmgeb/values/ValuesHelper.java
  • src/main/java/ksbysample/webapp/bootnpmgeb/values/validation/ValuesEnumValidator.java

Rather than using a lot of String arguments, consider using a container object for those values.

UseObjectForClearerAPI のルール違反のメッセージです。

メソッドの引数が多い場合に Parameter クラスを新規作成してそちらでも渡せるようにした方がよい、というルールのようです。今のところ必要性を感じなかったので exclude します。

The method '...' has a cyclomatic complexity of ....

CyclomaticComplexity のルール違反のメッセージです。

1メソッド内の行数(処理量?)が多いと出るメッセージで有効だと思うのですが、今回は exclude します。

以上で category/java/codestyle.xml は以下のような定義になりました。

    <rule ref="category/java/design.xml">
        <exclude name="LoosePackageCoupling"/>
        <exclude name="UseUtilityClass"/>
        <exclude name="NcssCount"/>
        <exclude name="LawOfDemeter"/>
        <exclude name="DataClass"/>
        <exclude name="UseObjectForClearerAPI"/>
        <exclude name="CyclomaticComplexity"/>
    </rule>

ここまでやってみた感想

今回は Rule の exclude 有無を1つずつ見直していますが、単純にバージョンアップするなら以下の方針で構わないと思います。

  • build.gradle の記述については特に変更する必要はありません。
  • 独自の Rulesets は書き方が変わっているので書き直しが必要。https://github.com/pmd/pmd/tree/master/pmd-java/src/main/resources/category/java の category の一覧を見て、xml ファイルに <rule ref="category/java/bestpractices.xml"/> のフォーマットで全て列挙します。
  • その後に build タスクを実行して、メッセージが出力された Rule を全て exclude します。
  • 6 以降に追加された Rule は exclude されたままにせず確認して追加するのか exclude したままにするのか判断します。

履歴

2018/06/03
初版発行。