ねこじゃすり
猫を魅了する魔法の装備品!
Anker PowerCor
旅行には必須の大容量モバイルバッテリー!
【最新機種】GoPro hero11 Black
最新機種でVlogの思い出を撮影しよう!
ペヤング ソースやきそば 120g×18個
とりあえず保存食として買っておけば間違いなし!
ドラゴンクエスト メタリックモンスターズギャラリー メタルキング
みんな大好き経験値の塊をデスクに常備しておこう!
MOFT X 【新型 ミニマム版】 iPhone対応 スマホスタンド
Amazon一番人気のスマホスタンド!カード類も収納出来てかさ張らないのでオススメです!
サンディスク microSD 128GB
スマホからSwitchまで使える大容量MicroSDカード!
スポンサーリンク
目次
scalaテンプレートでバリデーションエラーを取ろうとするとエラーが発生する
とある案件にてPlayFramework2.7でWebシステムを開発している際に、バリデーションエラーを画面に表示しようと公式のように実装していたところ、以下のようなエラーが発生しました。
| 1 2 3 4 5 6 7 8 9 10 11 12 | [error] a.a.ActorSystemImpl - Internal server error, sending 500 response akka.http.impl.util.One2OneBidiFlow$OutputTruncationException: Inner flow was completed without producing result elements for 1 outstanding elements 	at akka.http.impl.util.One2OneBidiFlow$OutputTruncationException$.apply(One2OneBidiFlow.scala:22) 	at akka.http.impl.util.One2OneBidiFlow$OutputTruncationException$.apply(One2OneBidiFlow.scala:22) 	at akka.http.impl.util.One2OneBidiFlow$One2OneBidi$$anon$1$$anon$4.onUpstreamFinish(One2OneBidiFlow.scala:97) 	at akka.stream.impl.fusing.GraphInterpreter.processEvent(GraphInterpreter.scala:506) 	at akka.stream.impl.fusing.GraphInterpreter.execute(GraphInterpreter.scala:376) 	at akka.stream.impl.fusing.GraphInterpreterShell.runBatch(ActorGraphInterpreter.scala:606) 	at akka.stream.impl.fusing.GraphInterpreterShell$AsyncInput.execute(ActorGraphInterpreter.scala:485) 	at akka.stream.impl.fusing.GraphInterpreterShell.processEvent(ActorGraphInterpreter.scala:581) 	at akka.stream.impl.fusing.ActorGraphInterpreter.akka$stream$impl$fusing$ActorGraphInterpreter$$processEvent(ActorGraphInterpreter.scala:749) 	at akka.stream.impl.fusing.ActorGraphInterpreter$$anonfun$receive$1.applyOrElse(ActorGraphInterpreter.scala:764) | 
ログを見ただけではちょっとエラーがまだ特定出来ませんね。
まずはデバッグでソースを追いながら原因を特定して対応していこうと思います
手順
前提
今回は以下のような一般的な独自アノテーションによるバリデーションを行おうと思っています。
インタフェース
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | package validations.auth; import static java.lang.annotation.ElementType.FIELD; import static java.lang.annotation.ElementType.METHOD; import static java.lang.annotation.RetentionPolicy.RUNTIME; import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.Target; import javax.validation.Constraint; import javax.validation.Payload; @Documented @Constraint(validatedBy = {LoginCheckValidator.class}) @Target({FIELD, METHOD}) @Retention(RUNTIME) public @interface LoginCheck {   String message() default "error.custom.loginCheck";   Class<?>[] groups() default {};   Class<? extends Payload>[] payload() default {}; } | 
実装クラス
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | package validations.auth; import javax.validation.ConstraintValidator; import javax.validation.ConstraintValidatorContext; import com.google.common.base.Strings; import lombok.Builder; import lombok.Data; public class LoginCheckValidator     implements ConstraintValidator<LoginCheck, LoginCheckValidator.Dto> {   public void initialize(LoginCheck loginCheck) {}   @Override   public boolean isValid(LoginCheckValidator.Dto value, ConstraintValidatorContext context) {     if (value.isNotFulfill()) {       return true;     }     boolean isValid = {何やのチェックロジック};     return isValid;   }   @Data   @Builder   public static class Dto {     private String loginId;     private String password;     public boolean isNotFulfill() {       return Strings.isNullOrEmpty(loginId) || Strings.isNullOrEmpty(password);     }   } } | 
Formモデル
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | package models.admin.login.form; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import play.data.validation.Constraints; import validations.auth.LoginCheck; import validations.auth.LoginCheckValidator; @AllArgsConstructor(staticName = "of") @Builder @Data public class LoginFormModel {   public LoginFormModel() {};   @Constraints.Required public String loginId;   @Constraints.Required public String password;   @LoginCheck   public LoginCheckValidator.Dto getLoginCheck() {     return LoginCheckValidator.Dto.builder()                 .loginId(this.loginId)                 .password(this.password)                 .build();   } } | 
login.scala.html
HTMLについては今回の必要な部分だけ抜粋しておきます。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | ... (略) ... <div class="form-group row mb-0">     <div class="col-md-8 offset-md-4">         <div>             <ul class="list-group list-group-flush">                 @for(error <- response.validationError("loginCheck").errors) {                 <li class="list-group-item">@error.format(messages)</li>                 }             </ul>         </div>         <button type="submit" class="btn btn-primary">             ログイン         </button>     </div> </div> ... (略) ... | 
org.springframework.beans.AbstractNestablePropertyAccessorでエラー
デバッグでソースを追っていくと、怪しい部分がありました。
>org.springframework.beans.AbstractNestablePropertyAccessor.javaの620行目辺りのPropertyHandler ph = getLocalPropertyHandler(actualName);にてnullが帰って来ておりそのあとのハンドリングでthrow new NotReadablePropertyException(getRootClass(), this.nestedPath + propertyName);とされています。
getLocalPropertyHandler
では、getLocalPropertyHandlerメソッドでは何をやっているのでしょうか。
ざっくり言うと、LoginFormModel.javaに定義されているフィールド名をmap形式で持っており、引数で渡って来たpropertyNameの存在をチェックしていますね。

今回は、フィールドは定義せずにgetterにアノテーションを付与してバリデーションを実現しています。
実際カスタムBeanValidator自体は正常に動いているので、そのあとのエラーメッセージのバインディングの部分でメソッドに対するBeanValidatorをサポートしていないようですね。
フィールドを定義する事で回避可能
取り急ぎ原因は分かりました。
解決するには、以下のように同名のフィールドを定義する事で正常に動くようになりました。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | package models.admin.login.form; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import play.data.validation.Constraints; import validations.auth.LoginCheck; import validations.auth.LoginCheckValidator; @AllArgsConstructor(staticName = "of") @Builder @Data public class LoginFormModel {   public LoginFormModel() {};   @Constraints.Required public String loginId;   @Constraints.Required public String password;   public LoginCheckValidator.Dto loginCheck;   @LoginCheck   public LoginCheckValidator.Dto getLoginCheck() {     return LoginCheckValidator.Dto.builder().loginId(this.loginId).password(this.password).build();   } } | 

loginCheckフィールド自体はフォームからは飛んでくる値ではないので、かなり気持ち悪いですが一旦はこれで解決しました。
何かもっと良い方法や筆者の認識や使い方が間違っている等あればご指摘頂ければなと思いますorz
PlayFrameworkには別の方法でカスタムバリデーションを実装出来る
ちなみに、PlayFrameworkではアノテーションではなくvalidateメソッドの@Overrideによって実現する事も出来ます。
Sometimes you need more sophisticated validation processes. E.g. when a user signs up you want to check if his email address already exists in the database and if so validation should fail.
Because constraints support both runtime Dependency Injection and , we can easily create our own custom (class-level) constraint which gets a Database object injected – which we can use later in the validation process. Of course you can also inject other components like MessagesApi, JPAApi, etc.Java Forms – 2.7.x
しかし、個人的な意見としては使い勝手はいまいちなのと複雑なのであまり使いたくありません。。。
終わりに
以上のように、フレームワークのかなりコアな部分が原因でした。
割と致命的な挙動だと思うので、お困りの方がいましたら是非試してみてください♪
 
									
 
			 
			 
			 
			 
			 
			





 
						 
											 
											 
											 
											 
											 
			 
			 
			 
			 
			 
											 
											 
											 
											 
											 
											 
											 
											 
											 
											 
               
               
               
               
               Travenist
Travenist 
       
       
       Otakenist
Otakenist 
       
       
      