かんがるーさんの日記

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

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

概要

記事一覧はこちらです。

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

  • 今回の手順で確認できるのは以下の内容です。
    • 確認画面の作成
    • サーバ側のテストを作成します。2回に分けます。

参照したサイト・書籍

目次

  1. pmd-project-rulesets.xml を変更する
  2. SessionData2ConfirmFormTypeMap クラスのテストを作成する
  3. SessionData2InquiryDataTypeMap クラスのテストを作成する
  4. InquiryMailHelper クラスのテストを作成する

手順

pmd-project-rulesets.xml を変更する

ksbysample.webapp.bootnpmgeb.helper.mail.InquiryMailHelper#generateTextUsingVelocity の Map<String, Object> model = new HashMap<>(); の部分に対して PMD が UseConcurrentHashMap の警告を出してくるのですが、マルチスレッドでアクセスする部分ではないのと、このアプリでは ConcurrentHashMap の使用を考慮する必要はないので、UseConcurrentHashMap の Rule を除外します。

config/pmd/pmd-project-rulesets.xml の以下の点を変更します。

    <rule ref="category/java/multithreading.xml">
        <exclude name="UseConcurrentHashMap"/>
    </rule>
  • <exclude name="UseConcurrentHashMap"/> を追加します。

SessionData2ConfirmFormTypeMap クラスのテストを作成する

src/main/java/ksbysample/webapp/bootnpmgeb/mapper/SessionData2ConfirmFormTypeMap.java で Ctrl+Shift+T を押して「Create Test」ダイアログを表示してから、以下の画像の値にした後「OK」ボタンをクリックします。

f:id:ksby:20180619014101p:plain

src/test/groovy/ksbysample/webapp/bootnpmgeb/mapper/SessionData2ConfirmFormTypeMapTest.groovy が新規作成されるので、以下の内容を記述します。

package ksbysample.webapp.bootnpmgeb.mapper

import ksbysample.webapp.bootnpmgeb.entity.SurveyOptions
import ksbysample.webapp.bootnpmgeb.helper.db.SurveyOptionsHelper
import ksbysample.webapp.bootnpmgeb.session.SessionData
import ksbysample.webapp.bootnpmgeb.values.JobValues
import ksbysample.webapp.bootnpmgeb.values.SexValues
import ksbysample.webapp.bootnpmgeb.values.Type1Values
import ksbysample.webapp.bootnpmgeb.values.Type2Values
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.ModelMapper
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import spock.lang.Specification

import java.util.stream.Collectors

@SpringBootTest
class SessionData2ConfirmFormTypeMapTest extends Specification {

    @Autowired
    private ModelMapper modelMapper

    @Autowired
    private SurveyOptionsHelper soh

    InquiryInput01Form inquiryInput01Form
    InquiryInput02Form inquiryInput02Form
    InquiryInput03Form inquiryInput03Form
    SessionData sessionData

    def setup() {
        inquiryInput01Form = new InquiryInput01Form(
                lastname: "田中"
                , firstname: "太郎"
                , lastkana: "たなか"
                , firstkana: "たろう"
                , sex: SexValues.MALE.value
                , age: "30"
                , job: JobValues.EMPLOYEE.value
        )
        inquiryInput02Form = new InquiryInput02Form(
                zipcode1: "100"
                , zipcode2: "0005"
                , address: "東京都千代田区丸の内"
                , tel1: "03"
                , tel2: "1234"
                , tel3: "5678"
                , email: "test@sample.co.jp"
        )
        inquiryInput03Form = new InquiryInput03Form(
                type1: Type1Values.PRODUCT.value
                , type2: [Type2Values.CATALOGUE.value
                          , Type2Values.ESTIMATE.value
                          , Type2Values.OTHER.value]
                , inquiry: "これは\r\nテスト\r\nです。"
                , survey: soh.selectItemList("survey").stream()
                .map { SurveyOptions surveyOptions -> surveyOptions.itemValue }
                .collect()
        )
        sessionData = new SessionData(
                inquiryInput01Form: inquiryInput01Form
                , inquiryInput02Form: inquiryInput02Form
                , inquiryInput03Form: inquiryInput03Form
        )
    }

    def "全ての項目に値がセットされている場合のテスト"() {
        setup:
        ConfirmForm confirmForm = modelMapper.map(sessionData, ConfirmForm)

        expect:
        confirmForm.name == "田中 太郎"
        confirmForm.kana == "たなか たろう"
        confirmForm.sex == SexValues.MALE.text
        confirmForm.age == "30"
        confirmForm.job == JobValues.EMPLOYEE.text
        confirmForm.zipcode == "100-0005"
        confirmForm.address == "東京都千代田区丸の内"
        confirmForm.tel == "03-1234-5678"
        confirmForm.email == "test@sample.co.jp"
        confirmForm.type1 == Type1Values.PRODUCT.text
        confirmForm.type2 == [Type2Values.CATALOGUE.text
                              , Type2Values.ESTIMATE.text
                              , Type2Values.OTHER.text].stream()
                .collect(Collectors.joining("、"))
        confirmForm.inquiry == "これは\r\nテスト\r\nです。"
        confirmForm.survey == soh.selectItemList("survey").stream()
                .map { SurveyOptions surveyOptions -> surveyOptions.itemName }
                .collect()
    }

    def "任意項目には値がセットされていない場合のテスト"() {
        setup:
        inquiryInput01Form.job = StringUtils.EMPTY
        inquiryInput02Form.tel1 = StringUtils.EMPTY
        inquiryInput02Form.tel2 = StringUtils.EMPTY
        inquiryInput02Form.tel3 = StringUtils.EMPTY
        inquiryInput03Form.survey = []
        ConfirmForm confirmForm = modelMapper.map(sessionData, ConfirmForm)

        expect:
        confirmForm.name == "田中 太郎"
        confirmForm.kana == "たなか たろう"
        confirmForm.sex == SexValues.MALE.text
        confirmForm.age == "30"
        confirmForm.job == StringUtils.EMPTY
        confirmForm.zipcode == "100-0005"
        confirmForm.address == "東京都千代田区丸の内"
        confirmForm.tel == StringUtils.EMPTY
        confirmForm.email == "test@sample.co.jp"
        confirmForm.type1 == Type1Values.PRODUCT.text
        confirmForm.type2 == [Type2Values.CATALOGUE.text
                              , Type2Values.ESTIMATE.text
                              , Type2Values.OTHER.text].stream()
                .collect(Collectors.joining("、"))
        confirmForm.inquiry == "これは\r\nテスト\r\nです。"
        confirmForm.survey == []
    }

}

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

f:id:ksby:20180619025929p:plain

SessionData2InquiryDataTypeMap クラスのテストを作成する

src/main/java/ksbysample/webapp/bootnpmgeb/mapper/SessionData2InquiryDataTypeMap.java で Ctrl+Shift+T を押して「Create Test」ダイアログを表示してから、以下の画像の値にした後「OK」ボタンをクリックします。

f:id:ksby:20180620004147p:plain

src/test/groovy/ksbysample/webapp/bootnpmgeb/mapper/SessionData2InquiryDataTypeMapTest.groovy が新規作成されるので、以下の内容を記述します。

package ksbysample.webapp.bootnpmgeb.mapper

import ksbysample.webapp.bootnpmgeb.dao.InquiryDataDao
import ksbysample.webapp.bootnpmgeb.entity.InquiryData
import ksbysample.webapp.bootnpmgeb.entity.SurveyOptions
import ksbysample.webapp.bootnpmgeb.helper.db.SurveyOptionsHelper
import ksbysample.webapp.bootnpmgeb.session.SessionData
import ksbysample.webapp.bootnpmgeb.values.JobValues
import ksbysample.webapp.bootnpmgeb.values.SexValues
import ksbysample.webapp.bootnpmgeb.values.Type1Values
import ksbysample.webapp.bootnpmgeb.values.Type2Values
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.ModelMapper
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.util.StreamUtils
import spock.lang.Specification

import java.nio.charset.StandardCharsets
import java.sql.Clob
import java.util.stream.Collectors

@SpringBootTest
class SessionData2InquiryDataTypeMapTest extends Specification {

    @Autowired
    private ModelMapper modelMapper

    @Autowired
    private SurveyOptionsHelper soh

    @Autowired
    private InquiryDataDao inquiryDataDao

    InquiryInput01Form inquiryInput01Form
    InquiryInput02Form inquiryInput02Form
    InquiryInput03Form inquiryInput03Form
    SessionData sessionData
    Clob inquiryClob

    def setup() {
        inquiryInput01Form = new InquiryInput01Form(
                lastname: "田中"
                , firstname: "太郎"
                , lastkana: "たなか"
                , firstkana: "たろう"
                , sex: SexValues.MALE.value
                , age: "30"
                , job: JobValues.EMPLOYEE.value
        )
        inquiryInput02Form = new InquiryInput02Form(
                zipcode1: "100"
                , zipcode2: "0005"
                , address: "東京都千代田区丸の内"
                , tel1: "03"
                , tel2: "1234"
                , tel3: "5678"
                , email: "test@sample.co.jp"
        )
        inquiryInput03Form = new InquiryInput03Form(
                type1: Type1Values.PRODUCT.value
                , type2: [Type2Values.CATALOGUE.value
                          , Type2Values.ESTIMATE.value
                          , Type2Values.OTHER.value]
                , inquiry: "これは\r\nテスト\r\nです。"
                , survey: soh.selectItemList("survey").stream()
                .map { SurveyOptions surveyOptions -> surveyOptions.itemValue }
                .collect()
        )
        sessionData = new SessionData(
                inquiryInput01Form: inquiryInput01Form
                , inquiryInput02Form: inquiryInput02Form
                , inquiryInput03Form: inquiryInput03Form
        )
        inquiryClob = inquiryDataDao.createClob()
        inquiryClob.setString(1, "これは\r\nテスト\r\nです。")
    }

    def "全ての項目に値がセットされている場合のテスト"() {
        setup:
        InquiryData inquiryData = modelMapper.map(sessionData, InquiryData)

        expect:
        inquiryData.lastname == "田中"
        inquiryData.firstname == "太郎"
        inquiryData.lastkana == "たなか"
        inquiryData.firstkana == "たろう"
        inquiryData.sex == SexValues.MALE.value
        inquiryData.age == 30
        inquiryData.job == JobValues.EMPLOYEE.value
        inquiryData.zipcode1 == "100"
        inquiryData.zipcode2 == "0005"
        inquiryData.address == "東京都千代田区丸の内"
        inquiryData.tel1 == "03"
        inquiryData.tel2 == "1234"
        inquiryData.tel3 == "5678"
        inquiryData.email == "test@sample.co.jp"
        inquiryData.type1 == Type1Values.PRODUCT.value
        inquiryData.type2 == [Type2Values.CATALOGUE.value
                              , Type2Values.ESTIMATE.value
                              , Type2Values.OTHER.value].stream()
                .collect(Collectors.joining(","))
        StreamUtils.copyToString(inquiryData.inquiry.asciiStream, StandardCharsets.UTF_8) ==
                StreamUtils.copyToString(inquiryClob.asciiStream, StandardCharsets.UTF_8)
        inquiryData.survey == soh.selectItemList("survey").stream()
                .map { SurveyOptions surveyOptions -> surveyOptions.itemValue }
                .collect(Collectors.joining(","))
    }

    def "任意項目には値がセットされていない場合のテスト"() {
        setup:
        inquiryInput01Form.job = StringUtils.EMPTY
        inquiryInput02Form.tel1 = StringUtils.EMPTY
        inquiryInput02Form.tel2 = StringUtils.EMPTY
        inquiryInput02Form.tel3 = StringUtils.EMPTY
        inquiryInput03Form.survey = []
        InquiryData inquiryData = modelMapper.map(sessionData, InquiryData)

        expect:
        inquiryData.lastname == "田中"
        inquiryData.firstname == "太郎"
        inquiryData.lastkana == "たなか"
        inquiryData.firstkana == "たろう"
        inquiryData.sex == SexValues.MALE.value
        inquiryData.age == 30
        inquiryData.job == StringUtils.EMPTY
        inquiryData.zipcode1 == "100"
        inquiryData.zipcode2 == "0005"
        inquiryData.address == "東京都千代田区丸の内"
        inquiryData.tel1 == StringUtils.EMPTY
        inquiryData.tel2 == StringUtils.EMPTY
        inquiryData.tel3 == StringUtils.EMPTY
        inquiryData.email == "test@sample.co.jp"
        inquiryData.type1 == Type1Values.PRODUCT.value
        inquiryData.type2 == [Type2Values.CATALOGUE.value
                              , Type2Values.ESTIMATE.value
                              , Type2Values.OTHER.value].stream()
                .collect(Collectors.joining(","))
        StreamUtils.copyToString(inquiryData.inquiry.asciiStream, StandardCharsets.UTF_8) ==
                StreamUtils.copyToString(inquiryClob.asciiStream, StandardCharsets.UTF_8)
        inquiryData.survey == StringUtils.EMPTY
    }

}

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

f:id:ksby:20180620232715p:plain

InquiryMailHelper クラスのテストを作成する

検証に使用する生成されたメール本文のデータを作成します。src/main/resources/ksbysample/webapp/bootnpmgeb/helper/mail のディレクトリを作成した後、InquiryMailHelperTest-001.txt, InquiryMailHelperTest-002.txt を新規作成し、以下の内容を記述します。

■InquiryMailHelperTest-001.txt

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

お名前(漢字):田中 太郎
お名前(かな):たなか たろう
性別:男性
年齢:30歳
職業:会社員
郵便番号:〒100-0005
住所:東京都千代田区丸の内
電話番号:03-1234-5678
メールアドレス:test@sample.co.jp

お問い合わせの種類1:製品に関するお問い合わせ
お問い合わせの種類2:資料が欲しい、見積が欲しい、その他の問い合わせ
お問い合わせの内容:
これは
テスト
です。
アンケート:
・選択肢1だけ長くしてみる
・選択肢2
・選択肢3
・選択肢4
・選択肢5が少し長い
・選択肢6
・選択肢7
・8

■InquiryMailHelperTest-002.txt

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

お名前(漢字):田中 太郎
お名前(かな):たなか たろう
性別:男性
年齢:30歳
職業:
郵便番号:〒100-0005
住所:東京都千代田区丸の内
電話番号:
メールアドレス:test@sample.co.jp

お問い合わせの種類1:製品に関するお問い合わせ
お問い合わせの種類2:資料が欲しい、見積が欲しい、その他の問い合わせ
お問い合わせの内容:
これは
テスト
です。
アンケート:

src/main/java/ksbysample/webapp/bootnpmgeb/helper/mail/InquiryMailHelper.java で Ctrl+Shift+T を押して「Create Test」ダイアログを表示してから、以下の画像の値にした後「OK」ボタンをクリックします。

f:id:ksby:20180620233923p:plain

src/test/groovy/ksbysample/webapp/bootnpmgeb/helper/mail/InquiryMailHelperTest.groovy が新規作成されるので、以下の内容を記述します。

package ksbysample.webapp.bootnpmgeb.helper.mail

import com.google.common.io.Files
import ksbysample.webapp.bootnpmgeb.entity.SurveyOptions
import ksbysample.webapp.bootnpmgeb.helper.db.SurveyOptionsHelper
import ksbysample.webapp.bootnpmgeb.values.JobValues
import ksbysample.webapp.bootnpmgeb.values.SexValues
import ksbysample.webapp.bootnpmgeb.values.Type1Values
import ksbysample.webapp.bootnpmgeb.values.Type2Values
import ksbysample.webapp.bootnpmgeb.web.inquiry.form.ConfirmForm
import org.apache.commons.lang3.StringUtils
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.core.io.ClassPathResource
import org.springframework.util.StreamUtils
import spock.lang.Specification

import javax.mail.Message
import javax.mail.internet.MimeMessage
import java.nio.charset.StandardCharsets
import java.util.stream.Collectors

@SpringBootTest
class InquiryMailHelperTest extends Specification {

    @Autowired
    private InquiryMailHelper inquiryMailHelper

    @Autowired
    private SurveyOptionsHelper soh

    ConfirmForm confirmForm

    def setup() {
        confirmForm = new ConfirmForm(
                name: "田中 太郎"
                , kana: "たなか たろう"
                , sex: SexValues.MALE.text
                , age: "30"
                , job: JobValues.EMPLOYEE.text
                , zipcode: "100-0005"
                , address: "東京都千代田区丸の内"
                , tel: "03-1234-5678"
                , email: "test@sample.co.jp"
                , type1: Type1Values.PRODUCT.text
                , type2: [Type2Values.CATALOGUE.text
                          , Type2Values.ESTIMATE.text
                          , Type2Values.OTHER.text].stream()
                .collect(Collectors.joining("、"))
                , inquiry: "これは\r\nテスト\r\nです。"
                , survey: soh.selectItemList("survey").stream()
                .map { SurveyOptions surveyOptions -> surveyOptions.itemName }
                .collect()
        )
    }

    def "全ての項目に値がセットされている場合のテスト"() {
        setup:
        MimeMessage message = inquiryMailHelper.createMessage(confirmForm)

        expect:
        message.getFrom()*.toString() == ["inquiry-form@sample.co.jp"]
        message.getRecipients(Message.RecipientType.TO)*.toString() == ["inquiry@sample.co.jp"]
        message.getSubject() == "問い合わせフォームからお問い合わせがありました"
        message.getContent() == Files.asCharSource(
                new File("src/test/resources/ksbysample/webapp/bootnpmgeb/helper/mail/InquiryMailHelperTest-001.txt")
                , StandardCharsets.UTF_8).read()
    }

    def "任意項目には値がセットされていない場合のテスト"() {
        setup:
        confirmForm.job = StringUtils.EMPTY
        confirmForm.tel = StringUtils.EMPTY
        confirmForm.survey = []
        MimeMessage message = inquiryMailHelper.createMessage(confirmForm)

        expect:
        message.getFrom()*.toString() == ["inquiry-form@sample.co.jp"]
        message.getRecipients(Message.RecipientType.TO)*.toString() == ["inquiry@sample.co.jp"]
        message.getSubject() == "問い合わせフォームからお問い合わせがありました"
        message.getContent() == StreamUtils.copyToString(
                new ClassPathResource("ksbysample/webapp/bootnpmgeb/helper/mail/InquiryMailHelperTest-002.txt")
                        .getInputStream(), StandardCharsets.UTF_8)
    }

}
  • Groovy だと message.getFrom()Address[] の戻り値を String の配列かリストに変えたい場合、Arrays.asList(...)stream().map() 等を使用しなくても *.toString() を付ければ String[] になります。

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

f:id:ksby:20180621010408p:plain

履歴

2018/06/23
初版発行。

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
初版発行。