hiddenタグ用ユーティリティ

現在のプロジェクトでは、画面で入力された内容はすべてhiddenで持ちまわるというルールに決めたが、入力項目が多い場合などにJSPにhiddenタグを記述するのが苦痛だという声があったので、アノテーションとタグライブラリでどうにかなるようにした。
まずはアノテーション。validator用のアノテーションよろしくtargetを指定できる。

/**
 * Hiddenタグへの埋め込み対象を表すアノテーションです。
 * 
 * @author namiki
 * 
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@Documented
public @interface HiddenParam {
    /**
     * 埋め込み対象となるメソッド名を指定します。複数ある場合はカンマ区切りで指定します。
     */
    String target() default "";
}

続いてこの仕組みのキモとなるユーティリティクラス。

/**
 * 画面にhiddenタグでパラメータを埋め込むユーティリティクラスです。
 * 
 * @author namiki
 * 
 */
public class HiddenTagsUtils {
    /**
     * プライベートなコンストラクタ。<br>
     * 外部からインスタンスの生成は行えない。
     */
    private HiddenTagsUtils() {
    }
    /**
     * リクエストにHidden用パラメータを保存します。<br>
     * 保存する対象は{@link HiddenParam}が付与されているパラメータのみを対象とします。<br>
     * フォームが<code>null</code>の場合、リクエストからHidden用パラメータを削除します。
     * 
     * @param request
     *            HTTPリクエスト
     * @param form
     *            フォーム
     */
    @SuppressWarnings("unchecked")
    public static void saveHiddenParams(HttpServletRequest request, Object form) {

        if (null == form) {
            request.removeAttribute(Constants.HIDDEN_PARAMS_KEY);
            return;
        }

        Map<String, String> paramMap = new HashMap<String, String>();

        Field[] fields = form.getClass().getFields();

        S2ExecuteConfig config = S2ExecuteConfigUtil.getExecuteConfig();
        String methodName = config.getMethod().getName();
        
        for (Field field : fields) {
            HiddenParam param = field.getAnnotation(HiddenParam.class);
            
            if (null != param) {
                String[] array = StringUtils.split(param.target(), ",");
                if (0 != array.length) {
                    if (!ArrayUtils.contains(array, methodName)) {
                        continue;
                    }
                }
                Object fieldValue = FieldUtil.get(field, form);
                String value = null;
                if (fieldValue instanceof String) {
                    value = (String) fieldValue;
                } else if (fieldValue instanceof Boolean) {
                    value = ((Boolean) fieldValue).toString();
                } else if (fieldValue instanceof Number) {
                    value = (String.valueOf((Number) fieldValue));
                }
                if (StringUtils.isEmpty(value)) {
                    value = "";
                }
                paramMap.put(field.getName(), value);
            }
        }
        request.setAttribute(Constants.HIDDEN_PARAMS_KEY, paramMap);
    }
}

最後にカスタムタグ。

/**
 * リクエストに設定されているパラメータをhiddenタグとして画面に埋め込むタグライブラリです。
 * 
 * @author namiki
 * 
 */
public class HiddenParamsTag extends TagSupport {

    // serialVersionUID
    private static final long serialVersionUID = 1L;

    /**
     * {@inheritDoc}
     * @see TagSupport#doStartTag()
     */
    @SuppressWarnings("unchecked")
    @Override
    public int doStartTag() throws JspException {

        Map<String, String> hiddenParams = null;

        Object obj = pageContext.findAttribute(Constants.HIDDEN_PARAMS_KEY);
        if (null != obj) {

            hiddenParams = (Map<String, String>) obj;
            Iterator<String> itr = hiddenParams.keySet().iterator();

            while (itr.hasNext()) {

                String key = itr.next();

                TagUtils.getInstance().write(
                    pageContext,
                    "<input type=\"hidden\""
                        + " name=\""
                        + key
                        + "\""
                        + " value=\""
                        + hiddenParams.get(key)
                        + "\""
                        + ">\n");
            }

        }

        return EVAL_BODY_INCLUDE;
    }
}

この仕組みの場合、ActionからHiddenTagsUtils#saveHiddenParams()を明示的に呼び出してやらないといけないのが問題といえば問題。本来はInterceptorで捕まえればいいのかもしれないが、あんまりInterceptorで処理してしまうと、どこで処理が行われているのか見えなくてイヤだという声もあったので今回はこのままにしておいた。まだまだ改良の余地はあるが、今回これがあったお蔭でだいぶ楽になったと言われたので良かった。