SaStrutsのvalidationをJSON形式で返す

入力チェック等のValidationの処理をSaStruts側(アノテーションとか)で処理して結果を非同期で返す(JSON)処理を作ってみました。

必要なライブラリ

JSONフォーマッター

JSONIC http://jsonic.sourceforge.jp/
 

処理の流れ

 ・サーバーサイドにリクエストが送られてくる。
 ・SaStruts側でValidationチェックを行う。
 ・処理結果をActionWrapperクラスを継承したクラスでJSON形式にフォーマットしてレスポンスを返す。
 ・クライアントサイドでレスポンスを受け取りJSON形式からエラーメッセージのhtmlを生成して表示。
 

修正箇所(サーバーサイド)

SaStruts側の修正部分は、ActionWrapperクラスとS2RequestProcessorを修正というか
継承して、変更を加えたい箇所のメソッドをオーバーライドします。
あと、非同期で返す処理だということを認識させるためのアノテーションを作成と
S2RequestProcessorから新しく作成するRequestProcessorに変更するので
struts-config.xmlを修正します。
 
アノテーションクラス

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;

/**
 * <pre>
 * Ajax
 * 非同期通信でvalidationエラーを返すためのアノテーション
 * </pre>
 * @version 1.0.0
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Documented
public @interface Ajax {

	String input = "ajax";
}

 
ActionWrapperを継承したクラス。

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import net.arnx.jsonic.JSON;
import net.arnx.jsonic.JSONException;

import org.apache.struts.Globals;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMessage;
import org.apache.struts.action.ActionMessages;
import org.apache.struts.util.MessageResources;
import org.apache.struts.validator.Resources;
import org.seasar.framework.container.deployer.InstanceDefFactory;
import org.seasar.framework.util.MethodUtil;
import org.seasar.struts.action.ActionWrapper;
import org.seasar.struts.config.S2ActionMapping;
import org.seasar.struts.config.S2ExecuteConfig;
import org.seasar.struts.config.S2ValidationConfig;
import org.seasar.struts.util.ActionMessagesUtil;
import org.seasar.struts.util.RequestUtil;
import org.seasar.struts.util.ResponseUtil;

/**
 */
public class SaStrutsActionWrapper extends ActionWrapper {

	/**
	 * コンストラクタ
	 * @param actionMapping
	 */
	public SaStrutsActionWrapper(S2ActionMapping actionMapping) {
		super(actionMapping);
	}

	@Override
	protected ActionForward execute(HttpServletRequest request,
			S2ExecuteConfig executeConfig) {
		ActionMessages errors = new ActionMessages();

            List validationConfigs = executeConfig.getValidationConfigs();
            
            if (validationConfigs != null) {
                for (S2ValidationConfig cfg : validationConfigs) {
                    if (cfg.isValidator()) {
                        ActionMessages errors2 = validateUsingValidator(request,
                            executeConfig);
                        if (errors2 != null &amp;&amp; !errors2.isEmpty()) {
                            errors.add(errors2);
                            if (executeConfig.isStopOnValidationError()) {
                                return processErrors(errors, request, executeConfig);
                            }
                        }
                    } else {
                        Object target = actionForm;
                        if (cfg.getValidateMethod().getDeclaringClass()
                            .isAssignableFrom(
                                    actionMapping.getComponentDef()
                                            .getComponentClass())) {
                             target = action;
                        }
                        ActionMessages errors2 = (ActionMessages) MethodUtil
                            .invoke(cfg.getValidateMethod(), target, null);
                        if (errors2 != null &amp;&amp; !errors2.isEmpty()) {
                            errors.add(errors2);
                            if (executeConfig.isStopOnValidationError()) {
                        	// @executeのinputがajaxだった場合
                    		if (executeConfig.getInput().equals("ajax")) {
                    			// JSON形式のERROR MSGを作成
                    			createJsonErrMsg(errors, request);
                    			return null;
                    		}
                                return processErrors(errors, request, executeConfig);
                            }
                        }
                    }
                }
            }
            if (!errors.isEmpty()) {
                return processErrors(errors, request, executeConfig);
            }
            String next = (String) MethodUtil.invoke(executeConfig.getMethod(),
                action, null);
            if (executeConfig.isRemoveActionForm()
                &amp;&amp; !ActionMessagesUtil.hasErrors(request)) {
                if (actionMapping.getActionFormComponentDef().getInstanceDef()
                    .equals(InstanceDefFactory.SESSION)) {
                    RequestUtil.getRequest().getSession().removeAttribute(
                        actionMapping.getActionFormComponentDef()
                                .getComponentName());
                } else {
                    RequestUtil.getRequest().removeAttribute(
                        actionMapping.getActionFormComponentDef()
                                .getComponentName());
                }
                RequestUtil.getRequest().removeAttribute(
                    actionMapping.getAttribute());
            } else {
        	if (ActionMessagesUtil.hasErrors(request)) {
        		// @executeのinputがajaxだった場合
        		if (executeConfig.getInput() != null &amp;&amp; executeConfig.getInput().equals("ajax")) {
        			errors.add((ActionMessages) request.getAttribute(Globals.ERROR_KEY));
        			// JSON形式のERROR MSGを作成
        			createJsonErrMsg(errors, request);
        			return null;
        		}
        	}
            }
            boolean redirect = executeConfig.isRedirect();
            if (redirect &amp;&amp; ActionMessagesUtil.hasErrors(request)) {
                redirect = false;
            }
            return actionMapping.createForward(next, redirect);
	}

	@Override
	protected ActionForward processErrors(ActionMessages errors,
			HttpServletRequest request, S2ExecuteConfig executeConfig) {
		// @executeのinputがajaxだった場合
		if (executeConfig.getInput().equals("ajax")) {
			// JSON形式のERROR MSGを作成
			createJsonErrMsg(errors, request);
			return null;
		}

		return super.processErrors(errors, request, executeConfig);
	}

	/**
	 * JSON形式のエラーメッセージを生成します。
	 * @param errors
	 * @param request
	 * @throws JSONException
	 */
	@SuppressWarnings("unchecked")
	private void createJsonErrMsg(ActionMessages errors, HttpServletRequest request)
			throws JSONException {
		// JSON形式で返す
		Map map = new HashMap();
		map.put("status", "validate");
                List<Object> list = new ArrayList<Object>();
                for (Iterator<String> it = errors.properties(); it.hasNext();) {
			Map<String, String> err = new HashMap<String, String>();
			String key = it.next();
			err.put("name", key);
			for (Iterator<ActionMessage> ite = errors.get(key); ite.hasNext();) {
				ActionMessage error = ite.next();
				System.out.println(error);
				MessageResources mResouces = Resources.getMessageResources(request);
				String msg = mResouces.getMessage((Locale)null, error.getKey(), error.getValues());
				System.out.println(msg);
				err.put("val", msg);
			}

			list.add(err);
                 }
		map.put("errors", list);
		ResponseUtil.write(JSON.encode(map), "application/json");
	}

}

 

S2RequestProcessorを継承したクラス

import java.io.IOException;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.struts.action.Action;
import org.apache.struts.action.ActionMapping;
import org.seasar.struts.action.S2RequestProcessor;
import org.seasar.struts.config.S2ActionMapping;

/**
 * <pre>
 * SaStrutsRequestProcessor
 *
 * </pre>
 * @version 1.0.0
 */
public class SaStrutsRequestProcessor extends S2RequestProcessor {

	/*
	 * (非 Javadoc)
	 * @see org.seasar.struts.action.S2RequestProcessor#processActionCreate(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, org.apache.struts.action.ActionMapping)
	 */
	@Override
	protected Action processActionCreate(HttpServletRequest request,
			HttpServletResponse response, ActionMapping mapping)
			throws IOException {

		Action action = null;
		try {
			action = new SaStrutsActionWrapper(((S2ActionMapping) mapping));
		} catch (Exception e) {
			log.error(getInternal().getMessage("actionCreate",
					mapping.getPath()), e);
			response
					.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
							getInternal().getMessage("actionCreate",
									mapping.getPath()));
			return null;
		}
		action.setServlet(servlet);
		return action;
	}
}


struts-config.xmlを修正

    <controller
        maxFileSize="1024K"
        bufferSize="1024"
        processorClass="xxx.xxx.xxxx.wrapper.SaStrutsRequestProcessor"
        multipartClass="org.seasar.struts.upload.S2MultipartRequestHandler"/>

 
Actionクラス
(validatorをtrueにして、inputをさっき作ったAjaxアノテーションクラスで指定

/**
 * ログイン処理アクション
 * @return 
 */
@Execute(validator = true, input = Ajax.input)
public String index(){

}

とりあえずjava側の修正は終了。

あとは、改めて。。。