OVal(アノテーションベースのバリデーションFW)のコード例

OValというアノテーションベースのアノテーションによる入力制限を付けれるバリデーションFWを使ってみました。
そのときのコード例をメモします。

# 依存関係
```xml:pom.xml
<!-- https://mvnrepository.com/artifact/net.sf.oval/oval -->
<dependency>
    <groupId>net.sf.oval</groupId>
    <artifactId>oval</artifactId>
    <version>1.90</version>
</dependency>
```

```groovy:build.gradle
// https://mvnrepository.com/artifact/net.sf.oval/oval
compile group: 'net.sf.oval', name: 'oval', version: '1.90'
```

# Springで使う場合
`net.sf.oval.integration.spring.SpringValidator`が
`org.springframework.validation.Validator`を実装しているらしいです。
まだ使っていないので、使ってみたらまたレポートします。

# 基本的なコードの例

```java:ビーンクラス
public class SampleBean{
    /** ID */
	@Assert(expr = "null!=_value&&''!=_value", lang = "js"
//lang = は"bsh" / "beanshell", "groovy", or "js" / "javascript".のどれか好きなの選んで使ってね
//この程度だったら@NotEmpty使えばよかったかも…
    		,message="IDが入力されてないです")
    private String id;

    /** No */
    private Integer no;

    /** 登録日 */
    @Assert(expr = "null!=_value&&''!=_value", lang = "js"
    		,message="登録日が入力されてないです")
    @MatchPattern(pattern=DateUtil.DATE_MATCH_PATTERN
    		,message="登録日の形式がおかしいです"
    		,when="js:null!=_value&&''!=_value")
    @DateRange(format=DateUtil.DATE_HH_MM_FORMAT,message="登録日の日付範囲がおかしいです")
    @ValidateWithMethod(methodName="isValidDate", parameterType=String.class)
			,message="１２月の日付を入力してください")
    private String abcTime;

    //getter,setter省略

    private boolean isValidDate(String arg){//12月だけtrue返すよ
        if(arg.matches("\d{4}/12/.*")){
            return true;
        }else{
            return false;
        }
    }
}
```

```java:バリデートの仕方
net.sf.oval.Validator validator = new net.sf.oval.Validator();
List<ConstraintViolation> clist =  validator.validate( validatedObject );
```

# staticメソッドでバリデーションしたい
という場合は標準機能にはないので、バリデートのアノテーションとチェックメソッドを自作します。
java8で書いたほうがスマートなんですが、java7の環境しか使わせてもらえないので(T-T)、java7のコードです。

```java:ValidateWithStaticMethod.java(アノテーション)
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import net.sf.oval.ConstraintTarget;
import net.sf.oval.ConstraintViolation;
import net.sf.oval.configuration.annotation.Constraint;
import net.sf.oval.configuration.annotation.Constraints;

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD, ElementType.TYPE })
@Constraint(checkWith = ValidateWithStaticMethodCheck.class)
public @interface ValidateWithStaticMethod {
    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD, ElementType.TYPE })
    @Constraints
    public @interface List {
        ValidateWithStaticMethod[] value();
        String when() default "";
    }

    ConstraintTarget[] appliesTo() default ConstraintTarget.CONTAINER;

    String errorCode() default "net.sf.oval.constraint.ValidateWithMethod";

    boolean ignoreIfNull() default true;

    boolean ignoreIfNullOrEmpty() default true;

    String message() default "メッセージが定義されていません(ValidateWithStaticMethod)";

    Class<?> clazz();

    String methodName();

    String[] profiles() default {};

    int severity() default 0;

    String target() default "";

    String when() default "";
}
```

```java:ValidateWithStaticMethodCheck.java(チェックロジック)

import static net.sf.oval.Validator.*;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;

import net.sf.oval.ConstraintTarget;
import net.sf.oval.Validator;
import net.sf.oval.configuration.annotation.AbstractAnnotationCheck;
import net.sf.oval.context.OValContext;
import net.sf.oval.exception.ReflectionException;

/**
 * Staticメソッドによるチェック
 */
public class ValidateWithStaticMethodCheck extends AbstractAnnotationCheck<ValidateWithStaticMethod> {
    private static final long serialVersionUID = 1L;

    private final Map<Class<?>, Method> validationMethodsByClass = new WeakHashMap<Class<?>, Method>();

    private boolean ignoreIfNull;
    private boolean ignoreIfNullOrEmpty;
    private Class<?> clazz;
    private String methodName;

    @Override
    public void configure(final ValidateWithStaticMethod constraintAnnotation) {
        super.configure(constraintAnnotation);
        setMethodName(constraintAnnotation.methodName());
        setClazz(constraintAnnotation.clazz());
        setIgnoreIfNull(constraintAnnotation.ignoreIfNull());
        setIgnoreIfNullOrEmpty(constraintAnnotation.ignoreIfNullOrEmpty());
    }

    @Override
    protected Map<String, String> createMessageVariables() {
        final Map<String, String> messageVariables = getCollectionFactory().createMap(4);
        messageVariables.put("ignoreIfNull", Boolean.toString(ignoreIfNull));
        messageVariables.put("ignoreIfNullOrEmpty", Boolean.toString(ignoreIfNullOrEmpty));
        messageVariables.put("methodName", methodName);
        return messageVariables;
    }

    @Override
    protected ConstraintTarget[] getAppliesToDefault() {
        return new ConstraintTarget[] { ConstraintTarget.VALUES };
    }

    public String getMethodName() {
        return methodName;
    }

    public boolean isIgnoreIfNull() {
        return ignoreIfNull;
    }

    public boolean isSatisfied(final Object validatedObject, final Object valueToValidate, final OValContext context, final Validator validator)
            throws ReflectionException {
        if (valueToValidate == null && ignoreIfNull)
            return true;
        if(ignoreIfNullOrEmpty){
        	if (valueToValidate == null)
        		return true;
        	//emptyの判定条件を追加したい場合、ここに追記してください
        	if ( valueToValidate instanceof String && "".equals(valueToValidate) )
        		return true;
        	if ( valueToValidate instanceof Object[] && 0==((Object[])valueToValidate).length )
        		return true;
        	if ( valueToValidate instanceof List && 0==((List<?>)valueToValidate).size() )
        		return true;
        }
        boolean result;
        Method method;
        try {
        	Class<?> parameterType = valueToValidate.getClass();
			method =  clazz.getMethod(methodName, parameterType);
	        result = (boolean)method.invoke(null, valueToValidate);
		} catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
			throw new ReflectionException( e );
		}
        return result;
    }

    public void setIgnoreIfNull(final boolean ignoreIfNull) {
        this.ignoreIfNull = ignoreIfNull;
        requireMessageVariablesRecreation();
    }


    public void setIgnoreIfNullOrEmpty(final boolean ignoreIfNullOrEmpty) {
        this.ignoreIfNullOrEmpty = ignoreIfNullOrEmpty;
        requireMessageVariablesRecreation();
    }

	public Class<?> getClazz() {
		return clazz;
	}

	public void setClazz(Class<?> clazz) {
		this.clazz = clazz;
	}

	public void setMethodName(final String methodName) {
        this.methodName = methodName;
        synchronized (validationMethodsByClass) {
            validationMethodsByClass.clear();
        }
        requireMessageVariablesRecreation();
    }

}
```

```java:アノテーションの付け方
//ValidationMethods.isFormatTelNo(String str)を使う場合のコード例
@ValidateWithStaticMethod(clazz=ValidationMethods.class
		,methodName="isFormatTelNo"
		,message="電話番号がおかしいです。。。")
private String telNo;
```

# 同じアノテーション複数つけたい

こう書けばいいみたいです。

```java
@ValidateWithStaticMethod.List(value={
		@ValidateWithStaticMethod(clazz=ValidationMethods.class
				,methodName="isFormatTelNo"
				,message="電話番号の形式がおかしいです"),
		@ValidateWithStaticMethod(clazz=ValidationMethods.class
				,methodName="isOkTelNumberSize"
				,message="電話番号の桁数がおかしいです")
})
private String telNo;
```

# 標準で提供されているアノテーション一覧

* [@Assert](http://oval.sourceforge.net/api/net/sf/oval/constraint/Assert.html)
* [@AssertConstraintSet](http://oval.sourceforge.net/api/net/sf/oval/constraint/AssertConstraintSet.html)
* [@AssertFalse](http://oval.sourceforge.net/api/net/sf/oval/constraint/AssertFalse.html)
* [@AssertFieldConstraints](http://oval.sourceforge.net/api/net/sf/oval/constraint/AssertFieldConstraints.html)
* [@AssertNull](http://oval.sourceforge.net/api/net/sf/oval/constraint/AssertNull.html)
* [@AssertTrue](http://oval.sourceforge.net/api/net/sf/oval/constraint/AssertTrue.html)
* [@AssertURL](http://oval.sourceforge.net/api/net/sf/oval/constraint/AssertURL.html)
* [@AssertValid](http://oval.sourceforge.net/api/net/sf/oval/constraint/AssertValid.html)
* [@CheckWith](http://oval.sourceforge.net/api/net/sf/oval/constraint/CheckWith.html)
* [@DateRange](http://oval.sourceforge.net/api/net/sf/oval/constraint/DateRange.html)
* [@Digits](http://oval.sourceforge.net/api/net/sf/oval/constraint/Digits.html)
* [@Email](http://oval.sourceforge.net/api/net/sf/oval/constraint/Email.html)
* [@EqualToField](http://oval.sourceforge.net/api/net/sf/oval/constraint/EqualToField.html)
* [@Future](http://oval.sourceforge.net/api/net/sf/oval/constraint/Future.html)
* [@HasSubstring](http://oval.sourceforge.net/api/net/sf/oval/constraint/HasSubstring.html)
* [@InstanceOf](http://oval.sourceforge.net/api/net/sf/oval/constraint/InstanceOf.html)
* [@InstanceOfAny](http://oval.sourceforge.net/api/net/sf/oval/constraint/InstanceOfAny.html)
* [@Length](http://oval.sourceforge.net/api/net/sf/oval/constraint/Length.html)
* [@MatchPattern](http://oval.sourceforge.net/api/net/sf/oval/constraint/MatchPattern.html)
* [@Max](http://oval.sourceforge.net/api/net/sf/oval/constraint/Max.html)
* [@MaxLength](http://oval.sourceforge.net/api/net/sf/oval/constraint/MaxLength.html)
* [@MaxSize](http://oval.sourceforge.net/api/net/sf/oval/constraint/MaxSize.html)
* [@MemberOf](http://oval.sourceforge.net/api/net/sf/oval/constraint/MemberOf.html)
* [@Min](http://oval.sourceforge.net/api/net/sf/oval/constraint/Min.html)
* [@MinLength](http://oval.sourceforge.net/api/net/sf/oval/constraint/MinLength.html)
* [@MinSize](http://oval.sourceforge.net/api/net/sf/oval/constraint/MinSize.html)
* [@NoSelfReference](http://oval.sourceforge.net/api/net/sf/oval/constraint/NoSelfReference.html)
* [@NotBlank](http://oval.sourceforge.net/api/net/sf/oval/constraint/NotBlank.html)
* [@NotEmpty](http://oval.sourceforge.net/api/net/sf/oval/constraint/NotEmpty.html)
* [@NotEqual](http://oval.sourceforge.net/api/net/sf/oval/constraint/NotEqual.html)
* [@NotEqualToField](http://oval.sourceforge.net/api/net/sf/oval/constraint/NotEqualToField.html)
* [@NotMatchPattern](http://oval.sourceforge.net/api/net/sf/oval/constraint/NotMatchPattern.html)
* [@NotMemberOf](http://oval.sourceforge.net/api/net/sf/oval/constraint/NotMemberOf.html)
* [@NotNegative](http://oval.sourceforge.net/api/net/sf/oval/constraint/NotNegative.html)
* [@NotNull](http://oval.sourceforge.net/api/net/sf/oval/constraint/NotNull.html)
* [@Past](http://oval.sourceforge.net/api/net/sf/oval/constraint/Past.html)
* [@Range](http://oval.sourceforge.net/api/net/sf/oval/constraint/Range.html)
* [@Size](http://oval.sourceforge.net/api/net/sf/oval/constraint/Size.html)
* [@ValidateWithMethod](http://oval.sourceforge.net/api/net/sf/oval/constraint/ValidateWithMethod.html)