Spring Boot + npm + Geb で入力フォームを作ってテストする ( その17 )( 入力画面1を作成する )
概要
記事一覧はこちらです。
Spring Boot + npm + Geb で入力フォームを作ってテストする ( その16 )( H2 Database に Flyway でテーブルを作成する ) の続きです。
- 今回の手順で確認できるのは以下の内容です。
- build すると Checkstyle, FindBugs, PMD が警告を出したので対応する
- 入力画面1の作成
参照したサイト・書籍
目次
- build したら Checkstyle, FindBugs, PMD が警告を出したので対応する
- input01.html 内の input タグに maxlength 属性を追加する
- Javascript 実装前に
npm run springboot
と Tomcat を起動して入力画面1を表示しようとしたら画面が表示されませんでした。。。 - 性別、職業の選択肢を表示する処理を追加する
- 続く。。。
手順
build したら Checkstyle, FindBugs, PMD が警告を出したので対応する
前回までの対応の後に build していなかったので build してみたところ、Checkstyle, FindBugs, PMD が警告を出したので対応します。
Javadoc タグには空でない説明文が必要です。
Doma-Gen で自動生成した Dao インターフェース src/main/java/ksbysample/webapp/bootnpmgeb/dao/InquiryDataDao.java の Javadoc のコメントで @param の説明文が空だったので警告が出ていました。
config/checkstyle/google_checks.xml に定義している module の <module name="NonEmptyAtclauseDescription"> ... </module>
によるチェックの結果のようです。
Doma-Gen で自動生成する時に自動でパラメータ名と同じ文字列を説明文に記述するようにします。build.gradle の domaGen タスクを以下のように変更します。
// for Doma-Gen task domaGen { doLast { .......... // 生成された Dao インターフェースを作業用ディレクトリにコピーし、 // @ComponentAndAutowiredDomaConfig アノテーションを付加し、 // Javadoc の @param に説明文を追加する copy() { from "${daoPackagePath}" into "${workDaoDirPath}/replace" filter { line -> line.replaceAll('import org.seasar.doma.Dao;', "import ${importOfComponentAndAutowiredDomaConfig};\nimport org.seasar.doma.Dao;") .replaceAll('@Dao', '@Dao\n@ComponentAndAutowiredDomaConfig') .replaceAll('@param (\\S+)$', '@param $1 $1') } } .......... } }
.replaceAll('@param (\\S+)$', '@param $1 $1')
を追加します。
InquiryDataDao.java と InquiryData.java を削除してから Tomcat を起動し、domaGen タスクを実行します。
自動生成された src/main/java/ksbysample/webapp/bootnpmgeb/dao/InquiryDataDao.java を見ると、以下のようにパラメータ名と同じ文字列がセットされています。
@Dao @ComponentAndAutowiredDomaConfig public interface InquiryDataDao { /** * @param id id * @return the InquiryData entity */ @Select InquiryData selectById(Integer id); ..........
Cannot open codebase filesystem:C:\project-springboot\ksbysample-boot-miscellaneous\boot-npm-geb-sample\build\classes\main\db\migration\V1__init.sql
build すると Flyway 用に追加した V1__init.sql
が build/classes/main/db/migration/V1__init.sql にコピーされるのですが、build.gradle の tasks.withType(FindBugs) { ... }
の中でこのファイルを FindBugs のチェック対象から除外していなかったため、以下のエラーが出ていました。
build.gradle の tasks.withType(FindBugs) { ... }
を以下のように変更します。
tasks.withType(FindBugs) { // Gradle 3.3以降 + FindBugs Gradle Plugin を組み合わせると、"The following errors occurred during analysis:" // の後に "Cannot open codebase filesystem:..." というメッセージが大量に出力されるので、以下の doFirst { ... } // のコードを入れることで出力されないようにする doFirst { def fc = classes if (fc == null) { return } fc.exclude '**/*.properties' fc.exclude '**/*.sql' fc.exclude '**/*.xml' fc.exclude '**/META-INF/**' fc.exclude '**/static/**' fc.exclude '**/templates/**' classes = files(fc.files) } reports { xml.enabled = false html.enabled = true } }
fc.exclude '**/*.sql'
を追加します。
Too many fields
Doma-Gen で自動生成した Entity クラス src/main/java/ksbysample/webapp/bootnpmgeb/entity/InquiryData.java のフィールド数が多いので、PMD の <rule ref="rulesets/java/codesize.xml"> ... </rule>
の TooManyFields で警告が出ていました。
Entity クラスを自動生成する時に @SuppressWarnings({"PMD.TooManyFields"})
を付加して PMD のチェック対象外になるようにします。build.gradle の domaGen タスクを以下のように変更します。
// for Doma-Gen task domaGen { doLast { // まず変更が必要なもの def rootPackageName = 'ksbysample.webapp.bootnpmgeb' def rootPackagePath = 'src/main/java/ksbysample/webapp/bootnpmgeb' def dbUrl = 'jdbc:h2:tcp://localhost:9092/mem:bootnpmgebdb' def dbUser = 'sa' def dbPassword = '' def tableNamePattern = 'INQUIRY_DATA' // おそらく変更不要なもの def entityPackagePath = rootPackagePath + '/entity' def daoPackagePath = rootPackagePath + '/dao' def importOfComponentAndAutowiredDomaConfig = "${rootPackageName}.util.doma.ComponentAndAutowiredDomaConfig" def workDirPath = 'work' def workEntityDirPath = "${workDirPath}/entity" def workDaoDirPath = "${workDirPath}/dao" // 作業用ディレクトリを削除する clearDir("${workDirPath}") // 現在の Dao インターフェースのバックアップを取得する copy() { from "${daoPackagePath}" into "${workDaoDirPath}/org" } // Dao インターフェース、Entity クラスを生成する ant.taskdef(resource: 'domagentask.properties', classpath: configurations.domaGenRuntime.asPath) ant.gen(url: "${dbUrl}", user: "${dbUser}", password: "${dbPassword}", tableNamePattern: "${tableNamePattern}") { entityConfig(packageName: "${rootPackageName}.entity", useListener: false) daoConfig(packageName: "${rootPackageName}.dao") sqlConfig() } // 生成された Entity クラスを作業用ディレクトリにコピーし、 // @SuppressWarnings({"PMD.TooManyFields"}) アノテーションを付加する copy() { from "${entityPackagePath}" into "${workEntityDirPath}/replace" filter { line -> line.replaceAll('@Entity', '@SuppressWarnings({"PMD.TooManyFields"})\n@Entity') } } // @SuppressWarnings({"PMD.TooManyFields"}) アノテーションを付加した Entity クラスを // entity パッケージへ戻す copy() { from "${workEntityDirPath}/replace" into "${entityPackagePath}" } ..........
- 以下の3行を追加します。
def rootPackagePath = 'src/main/java/ksbysample/webapp/bootnpmgeb'
def entityPackagePath = rootPackagePath + '/entity'
def workEntityDirPath = "${workDirPath}/entity"
- daoPackagePath は rootPackagePath を使うよう変更し、記述する位置を
// おそらく変更不要なもの
の下へ移動します。 copy() { from "${entityPackagePath}" ... }
を追加します。copy() { from "${workEntityDirPath}/replace" ... }
を追加します。
再度 InquiryDataDao.java と InquiryData.java を削除してから Tomcat を起動し直し、domaGen タスクを実行します。
自動生成された src/main/java/ksbysample/webapp/bootnpmgeb/entity/InquiryData.java を見ると、@SuppressWarnings({"PMD.TooManyFields"})
アノテーションが付加されています。
@SuppressWarnings({"PMD.TooManyFields"}) @Entity @Table(name = "INQUIRY_DATA") public class InquiryData {
以上の3点の警告を対応した後に build し直すと、今度は何も警告は表示されませんでした。
input01.html 内の input タグに maxlength 属性を追加する
INQUIRY_DATA テーブルのカラムに定義した文字数を src/main/resources/templates/web/inquiry/input01.html 内の <input type="text" .../>
に maxlength 属性として追加します。
Javascript 実装前に npm run springboot
と Tomcat を起動して入力画面1を表示しようとしたら画面が表示されませんでした。。。
Javascript の実装前にコマンドラインから npm run springboot
を実行し、Tomcat を起動します。
ブラウザから http://localhost:9080/inquiry/input/01/ にアクセスして入力画面1が表示されることを確認しようとしたら、画面が表示されませんでした。。。
CSRF 絡みですね。おそらく H2 Console を表示するために設定を変更したのが原因でしょう。
調べたら原因は http.csrf().requireCsrfProtectionMatcher(...)
で CSRF対策の対象にしない URL を設定する時には一緒に GET オプション等で CSRF対策が実行されないよう設定しないといけないからでした。Spring Boot で書籍の貸出状況確認・貸出申請する Web アプリケーションを作る ( その13 )( Spring Session を使用する2 ) で書いていたのに忘れていました。
src/main/java/ksbysample/webapp/bootnpmgeb/config/WebSecurityConfig.java を以下のように変更します。
@Configuration @Order(SecurityProperties.ACCESS_OVERRIDE_ORDER) public class WebSecurityConfig extends WebSecurityConfigurerAdapter { private static final Pattern DISABLE_CSRF_TOKEN_PATTERN = Pattern.compile("(?i)^(GET|HEAD|TRACE|OPTIONS)$"); private static final Pattern H2_CONSOLE_URI_PATTERN = Pattern.compile("^/h2-console"); .......... @Override protected void configure(HttpSecurity http) throws Exception { .......... // spring.h2.console.enabled=true に設定されている場合には H2 Console を表示するために必要な設定を行う if (springH2ConsoleEnabled) { http.csrf() .requireCsrfProtectionMatcher(request -> { if (DISABLE_CSRF_TOKEN_PATTERN.matcher(request.getMethod()).matches()) { // GET, HEAD, TRACE, OPTIONS は CSRF対策の対象外にする return false; } else if (H2_CONSOLE_URI_PATTERN.matcher(request.getRequestURI()).lookingAt()) { // H2 Console は CSRF対策の対象外にする return false; } return true; }); http.headers().frameOptions().sameOrigin(); } } }
private static final Pattern DISABLE_CSRF_TOKEN_PATTERN = Pattern.compile("(?i)^(GET|HEAD|TRACE|OPTIONS)$");
を追加します。if (DISABLE_CSRF_TOKEN_PATTERN.matcher(request.getMethod()).matches()) { ... }
を追加し、その次の if 文を else if に変更します。
Tomcat を再起動して http://localhost:9080/inquiry/input/01/ にアクセスすると今度は入力画面1が表示されました。
性別、職業の選択肢を表示する処理を追加する
src/main/java/ksbysample/webapp/bootnpmgeb の下に values パッケージを新規作成した後、ksbysample-webapp-lending から以下のファイルをコピーします。
- src/main/java/ksbysample/webapp/bootnpmgeb/values/Values.java
- src/main/java/ksbysample/webapp/bootnpmgeb/values/ValuesHelper.java
- src/main/java/ksbysample/webapp/bootnpmgeb/values/validation/ValuesEnum.java
- src/main/java/ksbysample/webapp/bootnpmgeb/values/validation/ValuesEnumValidator.java
src/main/resources/application-product.properties を以下のように変更します。
.......... valueshelper.classpath.prefix=BOOT-INF.classes.
- ファイルの末尾に
valueshelper.classpath.prefix=BOOT-INF.classes.
を追加します。
まずは「性別」からです。src/main/java/ksbysample/webapp/bootnpmgeb/values の下に SexValues.java を新規作成し、以下の内容を記述します。
package ksbysample.webapp.bootnpmgeb.values; import lombok.AllArgsConstructor; import lombok.Getter; @SuppressWarnings("MissingOverride") @Getter @AllArgsConstructor public enum SexValues implements Values { MALE("1", "男性") , FEMALE("2", "女性"); private final String value; private final String text; }
src/main/resources/templates/web/inquiry/input01.html の「性別」の部分を以下のように変更します。
<!-- 性別 --> <div class="form-group" id="form-group-sex"> <div class="control-label col-sm-2"> <label class="float-label">性別</label> <div class="label label-required">必須</div> </div> <div class="col-sm-10"> <div class="row"><div class="col-sm-10"> <div class="radio-inline" th:each="sexValue : ${@vh.values('SexValues')}"> <label><input type="radio" name="sex" th:value="${sexValue.value}" th:text="${sexValue.text}"></label> </div> </div></div> <div class="row hidden js-errmsg"><div class="col-sm-10"><p class="form-control-static text-danger"><small>ここにエラーメッセージを表示します</small></p></div></div> </div> </div>
<div class="radio-inline" ...>
にth:each="sexValue : ${@vh.values('SexValues')}"
を追加します。- ラジオボタンの各選択肢を記述していた HTML を
<label><input type="radio" name="sex" th:value="${sexValue.value}" th:text="${sexValue.text}"></label>
に変更します。
次は「職業」です。src/main/java/ksbysample/webapp/bootnpmgeb/values の下に JobValues.java を新規作成し、以下の内容を記述します。
package ksbysample.webapp.bootnpmgeb.values; import lombok.AllArgsConstructor; import lombok.Getter; @SuppressWarnings("MissingOverride") @Getter @AllArgsConstructor public enum JobValues implements Values { EMPLOYEE("1", "会社員") , STUDENT("2", "学生") , OTHER("3", "その他"); private final String value; private final String text; }
src/main/resources/templates/web/inquiry/input01.html の「職業」の部分を以下のように変更します。
<!-- 職業 --> <div class="form-group" id="form-group-job"> <div class="control-label col-sm-2"> <label class="float-label">職業</label> </div> <div class="col-sm-10"> <div class="row"><div class="col-sm-10"> <select name="job" id="job" class="form-control" style="width: 250px;"> <th:block th:each="jobValue,iterStat : ${@vh.values('JobValues')}"> <option th:if="${iterStat.first}" value="">選択してください</option> <option th:value="${jobValue.value}" th:text="${jobValue.text}">会社員</option> </th:block> </select> </div></div> <div class="row hidden js-errmsg"><div class="col-sm-10"><p class="form-control-static text-danger"><small>ここにエラーメッセージを表示します</small></p></div></div> </div> </div>
- 以下の3行を追加します。
<th:block th:each="jobValue,iterStat : ${@vh.values('JobValues')}">
<option th:if="${iterStat.first}" value="">選択してください</option>
</th:block>
<option>
にth:value="${jobValue.value}" th:text="${jobValue.text}"
を追加します。
画面を表示すると「性別」「職業」どちらも問題なく表示されています。
続く。。。
記事が長くなったので、あと1~2回くらいに分けて書きます。
最後に結構大きく修正した build.gradle の domaGen タスクを載せておきます。
ソースコード
build.gradle
// for Doma-Gen task domaGen { doLast { // まず変更が必要なもの def rootPackageName = 'ksbysample.webapp.bootnpmgeb' def rootPackagePath = 'src/main/java/ksbysample/webapp/bootnpmgeb' def dbUrl = 'jdbc:h2:tcp://localhost:9092/mem:bootnpmgebdb' def dbUser = 'sa' def dbPassword = '' def tableNamePattern = 'INQUIRY_DATA' // おそらく変更不要なもの def entityPackagePath = rootPackagePath + '/entity' def daoPackagePath = rootPackagePath + '/dao' def importOfComponentAndAutowiredDomaConfig = "${rootPackageName}.util.doma.ComponentAndAutowiredDomaConfig" def workDirPath = 'work' def workEntityDirPath = "${workDirPath}/entity" def workDaoDirPath = "${workDirPath}/dao" // 作業用ディレクトリを削除する clearDir("${workDirPath}") // 現在の Dao インターフェースのバックアップを取得する copy() { from "${daoPackagePath}" into "${workDaoDirPath}/org" } // Dao インターフェース、Entity クラスを生成する ant.taskdef(resource: 'domagentask.properties', classpath: configurations.domaGenRuntime.asPath) ant.gen(url: "${dbUrl}", user: "${dbUser}", password: "${dbPassword}", tableNamePattern: "${tableNamePattern}") { entityConfig(packageName: "${rootPackageName}.entity", useListener: false) daoConfig(packageName: "${rootPackageName}.dao") sqlConfig() } // 生成された Entity クラスを作業用ディレクトリにコピーし、 // @SuppressWarnings({"PMD.TooManyFields"}) アノテーションを付加する copy() { from "${entityPackagePath}" into "${workEntityDirPath}/replace" filter { line -> line.replaceAll('@Entity', '@SuppressWarnings({"PMD.TooManyFields"})\n@Entity') } } // @SuppressWarnings({"PMD.TooManyFields"}) アノテーションを付加した Entity クラスを // entity パッケージへ戻す copy() { from "${workEntityDirPath}/replace" into "${entityPackagePath}" } // 生成された Dao インターフェースを作業用ディレクトリにコピーし、 // @ComponentAndAutowiredDomaConfig アノテーションを付加し、 // Javadoc の @param に説明文を追加する copy() { from "${daoPackagePath}" into "${workDaoDirPath}/replace" filter { line -> line.replaceAll('import org.seasar.doma.Dao;', "import ${importOfComponentAndAutowiredDomaConfig};\nimport org.seasar.doma.Dao;") .replaceAll('@Dao', '@Dao\n@ComponentAndAutowiredDomaConfig') .replaceAll('@param (\\S+)$', '@param $1 $1') } } // @ComponentAndAutowiredDomaConfig アノテーションを付加した Dao インターフェースを // dao パッケージへ戻す copy() { from "${workDaoDirPath}/replace" into "${daoPackagePath}" } // 元々 dao パッケージ内にあったファイルを元に戻す copy() { from "${workDaoDirPath}/org" into "${daoPackagePath}" } // 作業用ディレクトリを削除する clearDir("${workDirPath}") } }
履歴
2017/08/26
初版発行。