Mapとのパラメータバインディング

入力コンポーネントが動的に増減するフォームを作りたい場合があります。リスト状の項目を、1つの画面内で一括編集したいときなどです。

こういうときはリクエストパラメータ名が動的に変化するので、パラメータをMapにバインディングできれば便利だと思い、Cubbyの処理をカスタマイズしてみました。

FormWrapperFactoryのカスタマイズ(Map → HTMLフォーム)

アクション内でMap型のフィールドにセットした初期値を、Cubbyのフォーム用カスタムタグ(など)から参照できるようにします。

org.seasar.cubby.controller.impl.FormWrapperFactoryImplを以下のように一部書き換えて、cubby.diconに登録します。

public class MappableFormWrapperFactory implements FormWrapperFactory {
    ...
    private class FormWrapperImpl implements FormWrapper {
        public String[] getValues(String name) {
            ...
            // 変更ここから
            Object value;
            if (form instanceof Map) {
                value = ((Map<?, ?>) form).get(name);
            }
            // 変更ここまで
            // 以下はBeanDescを使った従来通りの処理
            else {
                BeanDesc beanDesc = BeanDescFactory
                        .getBeanDesc(form.getClass());
                if (!beanDesc.hasPropertyDesc(name)) {
                    return null;
                }
                PropertyDesc propertyDesc = beanDesc.getPropertyDesc(name);
                value = propertyDesc.getValue(form);
            }
            ...
        }
    }
    ...

RequestParameterBinderのカスタマイズ(HTMLフォーム → Map)

アクションのsubmit時、@Formアノテーションバインディング先としてMap型のフィールドが指定された場合には、Mapにパラメータ名と値のペアを格納するようにします。値はとりあえず、無条件でStringに変換しています。

このクラスを、org.seasar.cubby.controller.impl.RequestParameterBinderImplの代わりにcubby.diconに登録します。

public class MappableRequestParameterBinder implements RequestParameterBinder {
    private RequestParameterBinderImpl defaultBinder;
    private ConverterFactory converterFactory;
    private ConversionHelper conversionHelper;

    public MappableRequestParameterBinder() {
        defaultBinder = new RequestParameterBinderImpl();
    }

    public void setConverterFactory(ConverterFactory converterFactory) {
        defaultBinder.setConverterFactory(converterFactory);
        this.converterFactory = converterFactory;
    }

    public void setConversionHelper(ConversionHelper conversionHelper) {
        defaultBinder.setConversionHelper(conversionHelper);
        this.conversionHelper = conversionHelper;
    }

    public void bind(Map<String, Object[]> parameterMap, Object dest,
            Class<? extends Action> actionClass, Method method) {
        if (!(dest instanceof Map)) {
            defaultBinder.bind(parameterMap, dest, actionClass, method);
            return;
        }

        Map<Object, Object> map = (Map<Object, Object>) dest;
        for (Entry<String, Object[]> entry : parameterMap.entrySet()) {
            String sourceName = entry.getKey();
            map.put(sourceName, convert(entry.getValue()));
        }
    }

    private Object convert(Object[] values) {
        Converter converter = converterFactory.getConverter(values[0]
                .getClass(), String.class);
        if (converter != null) {
            return converter.convertToObject(values[0], String.class,
                    conversionHelper);
        } else {
            return values[0];
        }
    }
}

submitされたパラメータが一律でStringになってしまったり、いまいちな感じもしますが、これで多少、動的なフォームが作りやすくなります。