
nablarch.common.web.validator.BeanValidationStrategy Maven / Gradle / Ivy
package nablarch.common.web.validator;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.servlet.ServletRequest;
import javax.validation.ConstraintViolation;
import javax.validation.Validator;
import nablarch.common.web.interceptor.InjectForm;
import nablarch.core.beans.BeanUtil;
import nablarch.core.beans.CopyOptions;
import nablarch.core.message.ApplicationException;
import nablarch.core.message.Message;
import nablarch.core.util.StringUtil;
import nablarch.core.util.annotation.Published;
import nablarch.core.validation.ValidationResultMessage;
import nablarch.core.validation.ee.ConstraintViolationConverterFactory;
import nablarch.core.validation.ee.ValidatorUtil;
import nablarch.fw.web.HttpRequest;
import nablarch.fw.web.servlet.ServletExecutionContext;
/**
* BeanValidationを使用する場合のリクエスト内容のバリデーション、オブジェクト(Bean)生成ロジック.
*
*
* 本実装ではバリデーションエラーが発生した場合に、
* リクエストパラメータから値をコピーしたオブジェクト(Bean)が、
* リクエストスコープに格納する機能を持つ。
* これは、バリデーションエラーが発生した時でも、JSP等でリクエストパラメータの値を
* 参照できるようにするためである。
* 本機能を有効化するには{@link #setCopyBeanToRequestScopeOnError(boolean)}に真を設定すること。
*
*
* @author sumida
*/
public class BeanValidationStrategy implements ValidationStrategy {
/** バリデーションエラー時にBeanをリクエストスコープにコピーするかどうか */
private boolean copyBeanToRequestScopeOnError = false;
/**
* フォームファクトリ。
* (デフォルトでは単純にリフレクションでインスタンスを生成する)
*/
private BeanValidationFormFactory formFactory = new SimpleReflectionBeanValidationFormFactory();
/**
* {@code BeanValidationStrategy}を生成する。
*/
@Published(tag = "architect")
public BeanValidationStrategy() { // NOP
}
public Serializable validate(HttpRequest request, InjectForm annotation, boolean notUse,
ServletExecutionContext context) {
Map rawRequestParamMap = request.getParamMap();
Map requestParamMap = getMapWithConvertedKey(annotation.prefix(), rawRequestParamMap);
Serializable bean = formFactory.create(annotation.form());
BeanUtil.copy(annotation.form(), bean, requestParamMap, CopyOptions.empty());
Validator validator = ValidatorUtil.getValidator();
Set> results = validator.validate(bean);
if (!results.isEmpty()) {
if (copyBeanToRequestScopeOnError) {
// エラーのとき、リクエストスコープにbeanを設定する
context.setRequestScopedVar(annotation.name(), bean);
}
List messages = new ConstraintViolationConverterFactory().create(annotation.prefix()).convert(results);
throw new ApplicationException(sortMessages(messages, context, annotation));
}
return bean;
}
/**
* メッセージをソートする。
*
* ソートされる順序は、{@link ServletRequest#getParameterNames()}の順となる。
* {@link ServletRequest#getParameterNames()}に存在しない項目は、メッセージリストの末尾に移動する。
*
* @param messages ソート対象のメッセージリスト
* @param context Servlet実行コンテキスト
* @param injectForm {@code InjectForm}アノテーション
* @return ソートしたメッセージリスト
*/
@Published(tag = "architect")
protected static List sortMessages(
final List messages, final ServletExecutionContext context, final InjectForm injectForm) {
final ServletRequest request = context.getServletRequest()
.getRequest();
@SuppressWarnings("unchecked")
final List parameterNames = Collections.list(request.getParameterNames());
final List sortedMessage = new ArrayList(messages);
Collections.sort(sortedMessage, new Comparator() {
@Override
public int compare(final Message m1, final Message m2) {
final int index1 = getParameterIndex(parameterNames, m1);
final int index2 = getParameterIndex(parameterNames, m2);
if (index1 < index2) {
// m1のほうが小さい場合
return -1;
} else if (index2 < index1) {
// m2のほうが小さい場合
return 1;
} else {
// それ以外は同じと扱う
return 0;
}
}
});
return sortedMessage;
}
/**
* メッセージが持つプロパティ名がパラメータ名の何番目の要素か返す。
*
* @param parameterNames パラメータ名のリスト
* @param message メッセージ
* @return パラメータ名の何番目か(パラメータ名にない場合はプロパティ名を持たない場合は{@link Integer#MAX_VALUE})
*/
private static int getParameterIndex(final List parameterNames, final Message message) {
if (message instanceof ValidationResultMessage) {
final int index = parameterNames.indexOf(((ValidationResultMessage) message).getPropertyName());
return index == -1 ? Integer.MAX_VALUE : index;
} else {
return Integer.MAX_VALUE;
}
}
/**
* {@link InjectForm}のprefixに指定した文字列を、キーの先頭から削除したMapオブジェクトを返す.
*
* @param prefix 項目名から削除するプレフィックス
* @param reqParamMap リクエストパラメータを格納したMap
* @return キーからprefixが削除されたMap
*/
private Map getMapWithConvertedKey(String prefix, Map reqParamMap) {
if (StringUtil.isNullOrEmpty(prefix)) {
// プレフィックスが指定されない場合、全てが対象とする
return new HashMap(reqParamMap);
}
final String prefixName = prefix + '.';
final int prefixLength = prefixName.length();
Map convertedMap = new HashMap();
for (Map.Entry entry : reqParamMap.entrySet()) {
final String key = entry.getKey();
final String[] value = entry.getValue();
if (key.startsWith(prefixName)) {
convertedMap.put(key.substring(prefixLength), value);
}
}
return convertedMap;
}
/**
* バリデーションエラー時に、Beanをリクエストスコープにコピーするかどうかを
* 設定する(デフォルトは「コピーしない」)。
*
* @param copyBeanToRequestScopeOnError コピーする場合は真を指定
*/
public void setCopyBeanToRequestScopeOnError(boolean copyBeanToRequestScopeOnError) {
this.copyBeanToRequestScopeOnError = copyBeanToRequestScopeOnError;
}
public void setFormFactory(BeanValidationFormFactory formFactory) {
this.formFactory = formFactory;
}
}