ログ日記

作業ログと日記とメモ

IComponentResolverとpropertiesファイルのtips

以前IComponentResolverについて書いたんだけど *1 *2 、この必須チェックが予想以上に便利で使いやすい。


それで、リスト表示でも使いたくなったので少しだけ汎用化した。


EntityComponentResolver.java

import java.lang.reflect.Field;

import javax.persistence.Column;

import org.apache.wicket.Component;
import org.apache.wicket.MarkupContainer;
import org.apache.wicket.markup.ComponentTag;
import org.apache.wicket.markup.MarkupStream;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.basic.MultiLineLabel;
import org.apache.wicket.markup.html.form.FormComponent;
import org.apache.wicket.markup.html.form.PasswordTextField;
import org.apache.wicket.markup.html.form.TextArea;
import org.apache.wicket.markup.html.form.TextField;
import org.apache.wicket.markup.resolver.IComponentResolver;

@SuppressWarnings("serial")
public class EntityComponentResolver<T> implements IComponentResolver {
    public interface Parent {
        public void setAutoAdd(Component component);
    }

    private static final String MULTI_LINE = "attr:multiline";

    private final Class<?> clazz; // clazzまたはentityのどちらかを使う
    private final T entity;
    private final Parent parent;
    public EntityComponentResolver(Parent parent, Class<?> clazz) {
        this.clazz = clazz;
        this.entity = null;
        this.parent = parent;
    }
    public EntityComponentResolver(Parent parent, T entity) {
        this.clazz = null;
        this.entity = entity;
        this.parent = parent;
    }

    @Override
    public Component resolve(MarkupContainer container,
            MarkupStream stream, ComponentTag tag) {
        if (tag.isAutoComponentTag())
            return null; // wicketが自動で追加するタグ


        String tagName = tag.getName().toLowerCase();
        if (tagName.equals("input")){
            if (tag.getAttribute("type").equals("text"))
                return addFormComponent(new TextField<Void>(tag.getId()), tag, container, stream);
            else if (tag.getAttribute("type").equals("password"))
                return addFormComponent(new PasswordTextField(tag.getId()).setResetPassword(false), 
                   tag, container, stream);

            throw new RuntimeException("Unknown input tag.");
        }else if (tagName.equals("textarea")){
            return addFormComponent(new TextArea<Void>(tag.getId()), tag, container, stream);

        }else{
            String multiline = tag.getAttribute(MULTI_LINE);
            Component component;
            if (multiline != null && multiline.equals("true"))
                component = new MultiLineLabel(tag.getId());
            else
                component = new Label(tag.getId());
            return component;
        }

    }

    /**
     * Entityのアノテーションを見て制約を設定する。
     * @param component
     * @param tag
     * @return
     */
    private Component addFormComponent(FormComponent<?> component, ComponentTag tag,
            MarkupContainer container, MarkupStream markupStream){
        try {
            Field f;
            if (clazz != null)
                f = clazz.getField(tag.getId());
            else
                f = entity.getClass().getField(tag.getId());
            Column col = f.getAnnotation(Column.class);
            if (col != null){
                if (!col.nullable())
                    component.setRequired(true);
            }
        } catch (SecurityException e) {

        } catch (NoSuchFieldException e) {
        }
        addForRender(component, container, markupStream);
        return component;
    }

    private void addForRender(Component component, MarkupContainer container,
            MarkupStream markupStream){
        if (markupStream != null){
            component.setMarkup(markupStream.getMarkupFragment());
        }

        parent.setAutoAdd(component);
    }
}


EntityComponent.java

import org.apache.wicket.Component;
import org.apache.wicket.MarkupContainer;
import org.apache.wicket.markup.ComponentTag;
import org.apache.wicket.markup.MarkupStream;
import org.apache.wicket.markup.html.WebMarkupContainer;
import org.apache.wicket.markup.resolver.IComponentResolver;
import org.apache.wicket.model.CompoundPropertyModel;

/**
 * エンティティクラスから自動でコンポーネントを生成してaddするコンテナ。
 *
 * <pre>有効な属性
 * - attr:multiline="true"  MultiLineLabelを生成する。
 * </pre>
 * @author nishimura
 * @param <T> Entityクラス
 */
@SuppressWarnings("serial")
public class EntityComponent<T> extends WebMarkupContainer implements IComponentResolver, EntityComponentResolver.Parent {
    private final EntityComponentResolver<T> entityComponentResolver;

    public EntityComponent(String id, T obj) {
        super(id, new CompoundPropertyModel<T>(obj));
        entityComponentResolver = new EntityComponentResolver<T>(this, obj);
    }

    @Override
    public Component resolve(MarkupContainer container, MarkupStream stream,
            ComponentTag tag) {
        return entityComponentResolver.resolve(container, stream, tag);
    }

    @Override
    public void setAutoAdd(Component component) {
        component.setParent(this);
        setAuto(true);
        add(component);
        setAuto(false);
    }
}


EntityDataView.java

import org.apache.wicket.Component;
import org.apache.wicket.MarkupContainer;
import org.apache.wicket.markup.ComponentTag;
import org.apache.wicket.markup.MarkupStream;
import org.apache.wicket.markup.repeater.Item;
import org.apache.wicket.markup.repeater.data.DataView;
import org.apache.wicket.markup.repeater.data.IDataProvider;
import org.apache.wicket.markup.resolver.IComponentResolver;
import org.apache.wicket.model.CompoundPropertyModel;

@SuppressWarnings("serial")
public class EntityDataView<T> extends DataView<T> implements IComponentResolver, EntityComponentResolver.Parent {
    private final EntityComponentResolver<T> entityComponentResolver;
    public EntityDataView(String id, IDataProvider<T> dataProvider, Class<T> clazz) {
        super(id, dataProvider);
        entityComponentResolver = new EntityComponentResolver<T>(this, clazz);
    }
    public EntityDataView(String id, IDataProvider<T> dataProvider, int itemsPerPage, Class<T> clazz) {
        super(id, dataProvider, itemsPerPage);
        entityComponentResolver = new EntityComponentResolver<T>(this, clazz);
    }

    @Override
    public Component resolve(MarkupContainer container,
            MarkupStream markupStream, ComponentTag tag) {
        return entityComponentResolver.resolve(container, markupStream, tag);
    }

    @Override
    protected void populateItem(Item<T> item) {
        item.setModel(new CompoundPropertyModel<T>(item.getModelObject()));
    }

    @Override
    public void setAutoAdd(Component component) {
        component.setParent(this);
        setAuto(true);
        add(component);
        setAuto(false);
    }

}

これでリスト表示にも使えるようになった。


あと共通のprpoertiesファイルにエンティティのフィールド名を書くとより使いやすくなる。


MyApplication.utf8.properties

entity.goods.name = 商品名
entity.goods.code = 商品番号


ListPage.html

<table>
  <tr>
    <th><wicket:message key="entity.goods.name">name</wicket:message></th>
    <th><wicket:message key="entity.goods.code">code</wicket:message></th>
  </tr>
  <tr wicket:id="goods">
    <td wicket:id="name">name</td>
    <td wicket:id="code">code</td>
  </tr>
</table>


EditPage.html

<form wicket:id="entity">
  <table wicket:id="goods">
    <tr>
      <th><wicket:message key="name">name</wicket:message></th>
      <td><input type="text" wicket:id="name"></td>
    </tr>
    <tr>
      <th><wicket:message key="code">code</wicket:message></th>
      <td><input type="text" wicket:id="code"></td>
    </tr>
  </table>
</form>

こんな感じで、表示用のラベルにも使えるしフォームのエラーメッセージにも利用できるので手間がだいぶ減った。