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

Xtend の @Property を使って BeanValidation でハマったこと

最近 JavaEE 触り始めてます。JSR 303: Bean Validation というアノテーションベースの検証機能があります。.NET でいうと System.ComponentModel.DataAnnotations 名前空間 () のような感じでしょうか。
BeanValidation の使い方等々の話しは無しで、Xtend - Modernized Java の Active Annotation @Property を使った際にハマった事を書きます。
Xtend の @Property はこんな Java コードに変換されます。

Hoge.xtend

class Hoge {
  @Property String name
}

Hoge.java

@SuppressWarnings("all")
public class Hoge {
  private String _name;
  
  public String getName() {
    return this._name;
  }
  
  public void setName(final String name) {
    this._name = name;
  }
}

これを踏まえて BeanValidation で検証機能を追加していきましょう。

Hoge.xtend

import javax.validation.constraints.*

class Hoge {
  @Property
  @NotNull
  @Size(min = 1, max = 10)
  String name
}

HogeText.xtend

import org.junit.Test
import static org.junit.Assert.*
import javax.validation.Validation

class HogeTest {
  @Test
  def void validation() {
    val validator = Validation.buildDefaultValidatorFactory.validator
    val target = new Hoge => [
      name = ""
    ]
    assertFalse(validator.validateProperty(target, "name").empty)
  }
}

本来失敗欲しいこのテストが成功してしまいます。
理由は生成された Hoge の Java コードを見ると分かります。

Hoge.java

import javax.validation.constraints.*

@SuppressWarnings("all")
public class Hoge {
  @NotNull
  @Size(min = 1, max = 10)
  private String _name;
  
  public String getName() {
    return this._name;
  }
  
  public void setName(final String name) {
    this._name = name;
  }
}

となっており、validationProperty で指定する場合は、"_name" と指定しなければなりません。または、 getName に BeanValidation のアノテーションを付ける必要があります。
validationProperty では存在しないプロパティ名を指定された場合例外が吐かれるようですが、getName があるため例外も吐かれません。


せっかく Xtend 使ってるのにこれは不便だ!ということで、@Property に似せた Active Annotation を書いてみました。

PropertyEx.xtend

import java.lang.annotation.ElementType
import java.lang.annotation.Target
import org.eclipse.xtend.lib.macro.AbstractFieldProcessor
import org.eclipse.xtend.lib.macro.Active
import org.eclipse.xtend.lib.macro.TransformationContext
import org.eclipse.xtend.lib.macro.declaration.MutableFieldDeclaration

@Target(ElementType.FIELD)
@Active(PropertyExProcessor)
annotation PropertyEx {
}
class PropertyExProcessor extends AbstractFieldProcessor {
  override doTransform(MutableFieldDeclaration field, extension TransformationContext context) {
    field.declaringType.addMethod('get' + field.simpleName.toFirstUpper) [
      returnType = field.type
      body = ['''return this.«field.simpleName»;''']
    ]
    field.declaringType.addMethod('set' + field.simpleName.toFirstUpper) [
      addParameter(field.simpleName, field.type)
        body = ['''this.«field.simpleName» = «field.simpleName»;''']
    ]
  }
}

ソースコード中の « » は、«» です。上手く表示してくなかった。。Xtend の Template Expressions の機能です。

これを使って Hoge.xtend を書き換えると…

Hoge.xtend

import javax.validation.constraints.*

class Hoge {
  @PropertyEx
  @NotNull
  @Size(min = 1, max = 10)
  String name
}

Hoge.java

import javax.validation.constraints.*

@SuppressWarnings("all")
public class Hoge {
  @NotNull
  @Size(min = 1, max = 10)
  private String name;
  
  public String getName() {
    return this.name;
  }
  
  public void setName(final String name) {
    this.name = name;
  }
}

となり、先ほどのテストも正しく失敗してくれます。


という訳で @Property + BeanValidation は気を付けた方が良さそうですね。