システム開発で思うところ

Javaで主にシステム開発をしながら思うところをツラツラを綴る。主に自分向けのメモ。EE関連の情報が少なく自分自身がそういう情報があったら良いなぁということで他の人の参考になれば幸い

JSFのSelectItemを拡張したEnumで実装する

前回、Enumを拡張しました
vermeer.hatenablog.jp

これを使ってJSFのSelectItemを実装します。

ちなみに、Enumだけでも十分対応は出来るようです。
【メモ】JSFでenumのオブジェクトをselectOneMenuに使う方法 - mike-neckのブログ
JSFのConverterで見た闇の話 - mike-neckのブログ

固定値であればEnumが良いですが、例えば何かしらの一覧をリストから選択したい場合、コード値とプロパティのペアを動的に設定したいことは良くあります。
今回、Enumに加えてMapも同様に扱うようにしました。

中途半端ではありますが、ResourceBundleも扱ってみました。*1*2

propertyはmessage.propertiesで設定にして表示文字を外部に持たせてます。
Map側はRDBに保存している値を使用する想定なので国際化対応はしない想定です。*3

以下の実装では、Enum has code and propertyの性別を変更して【更新】ボタンを押下したら、Mapのお菓子リストが更新されます。

実行対象のクラス・インターフェース・メッセージプロパティ

Enum
@AllArgsConstructor @Getter
public enum Gender implements EnumCodePropertyInterface {
    MALE(1, "gender_male"), FEMALE(2, "gender_female");

    private final Integer code;
    private final String property;

}
Interface
public interface EnumCodePropertyInterface {

    public Object getCode();

    public String getProperty();

}
Enum(拡張)のFactory
public class EnumCodeProperty {

    public static <E extends Enum<E> & EnumCodePropertyInterface> E codeOf(Class<E> enumType, @NonNull Object code) {
	for (E type : enumType.getEnumConstants()) {
	    if (type.getCode().equals(code)) {
		return type;
	    }
	}
	throw new IllegalArgumentException();
    }

    public static <E extends Enum<E> & EnumCodePropertyInterface> E propertyOf(Class<E> enumType, @NonNull String property) {
	for (E type : enumType.getEnumConstants()) {
	    if (type.getProperty().equals(property)) {
		return type;
	    }
	}
	throw new IllegalArgumentException();
    }
}
message.properties
noselect=選択無し
gender_male=男
gender_female=女
JsfのSelectItemのFactory

Enum も Map も同じメソッド名で使えるようにオーバーロードしています。

package com.mycompany.samplejsf.infrastructure.part.jsf;

@RequiredArgsConstructor(staticName = "of") @Value
public class JsfSelectItem {

    private final List<SelectItem> values;

    public static <E extends Enum<E> & EnumCodePropertyInterface> JsfSelectItem of(Class<E> enumClass) {
	List<SelectItem> items = new ArrayList<>();
	ResourceBundle bundle = ResourceBundle.getBundle("message");
	for (E item : enumClass.getEnumConstants()) {
	    String value = bundle.containsKey(item.getProperty())
			   ? bundle.getString(item.getProperty())
			   : item.getProperty();
	    SelectItem selectItem = new SelectItem(item.getCode(), value);
	    items.add(selectItem);
	}
	return JsfSelectItem.of(items);
    }

    public static JsfSelectItem of(Map<?, String> itemMap) {
	List<SelectItem> items = new ArrayList<>();
	itemMap.entrySet().stream()
		.forEachOrdered(map -> {
		    SelectItem item = new SelectItem(map.getKey(), map.getValue());
		    items.add(item);
		});
	return JsfSelectItem.of(items);
    }
}

実行資産

ManagedBean
package com.mycompany.samplejsf.domain.selectitem;

@Named(value = "selectItem")
@SessionScoped
@NoArgsConstructor @Getter
public class SelectItemController implements Serializable {

    private static final long serialVersionUID = 1L;
    @Setter
    private Integer selectItemValueDirectWrite;

    @Setter
    private Integer selectItemValueEnumCodeProperty;

    @Setter
    private Integer selectItemValueMap;

    private JsfSelectItem enumCodeProperty;
    private JsfSelectItem selectItemMap;

    private Map<Integer, String> dummuyMap;

    @PostConstruct
    public void init() {
	this.selectItemValueDirectWrite = GenderEnumOnly.MALE.ordinal();

	this.selectItemValueEnumCodeProperty = Gender.FEMALE.getCode();
	this.enumCodeProperty = JsfSelectItem.of(Gender.class);

	this.replaceSelectItemMap();
	this.printLog("init");
    }

    public void submit() {
	this.replaceSelectItemMap();
	this.printLog("submit");
    }

    private void replaceSelectItemMap() {
	if (this.selectItemValueEnumCodeProperty.equals(Gender.MALE.getCode())) {
	    this.maleSnack();
	    return;
	}
	this.femaleSnack();
    }

    private void maleSnack() {
	Map<Integer, String> map = new LinkedHashMap<>();
	map.put(1, "大福");
	map.put(2, "おはぎ");
	map.put(3, "みたらしだんご");
	map.put(4, "せんべい");
	this.selectItemValueMap = map.keySet().iterator().next();
	this.selectItemMap = JsfSelectItem.of(map);
	this.dummuyMap = map;
    }

    private void femaleSnack() {
	Map<Integer, String> map = new LinkedHashMap<>();
	map.put(5, "チョコ");
	map.put(6, "クッキー");
	map.put(7, "プリン");
	map.put(8, "ゼリー");
	this.selectItemValueMap = map.keySet().iterator().next();
	this.selectItemMap = JsfSelectItem.of(map);
	this.dummuyMap = map;
    }

    private void printLog(String label) {
	System.out.println("label = " + label);
	System.out.println("selectItemValueDirectWrite = " + selectItemValueDirectWrite);
	System.out.println("selectItemValueEnumCodeProperty = " + selectItemValueEnumCodeProperty);
	System.out.println("selectItemValueEnumCodeProperty codeOf = " + EnumCodeProperty.codeOf(Gender.class, selectItemValueEnumCodeProperty));
	System.out.println("selectItemValueMap = " + selectItemValueMap);
	System.out.println("selectItemValueMap value = " + this.dummuyMap.get(this.selectItemValueMap));
    }
}
xhtml
<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:h="http://xmlns.jcp.org/jsf/html"
      xmlns:ui="http://java.sun.com/jsf/facelets"
      xmlns:p="http://primefaces.org/ui">
    <head>
        <title>JSF SELECT ITEM</title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"></meta>
    </head>
    <body>
        <form jsfc="h:form" enctype="multipart/form-data">
            <h1>JSF SELECT ITEM PATTERN</h1>
            <dir>
                <h2>Direct Write</h2>
                <select jsfc="h:selectOneMenu" value="#{selectItem.selectItemValueDirectWrite}">
                    <option jsfc="f:selectItem" itemLabel="#{msg['gender_male']}" itemValue="0"></option>
                    <option jsfc="f:selectItem" itemLabel="#{msg['gender_female']}" itemValue="1"></option>
                </select>
            </dir>
            <hr />
            <dir>
                <h2>Enum has code and property </h2>
                <select jsfc="h:selectOneMenu"  value="#{selectItem.selectItemValueEnumCodeProperty}">
                    <option jsfc="f:selectItems" value="#{selectItem.enumCodeProperty.values}"></option>
                </select>
            </dir>
            <hr />
            <dir>
                <h2>Map</h2>
                <select jsfc="h:selectOneMenu"  value="#{selectItem.selectItemValueMap}">
                    <option jsfc="f:selectItems" value="#{selectItem.selectItemMap.values}"></option>
                </select>
            </dir>
            <hr />
            <dir>
                <input type="submit" jsfc="h:commandButton" value="更新" action="#{selectItem.submit()}"/>
            </dir>
        </form>
    </body>
</html>

実行結果

画面

f:id:vermeer-1977:20161219140146p:plain

コンソールログ
label = init
selectItemValueDirectWrite = 0
selectItemValueEnumCodeProperty = 2
selectItemValueEnumCodeProperty codeOf = FEMALE
selectItemValueMap = 5
selectItemValueMap value = チョコ
label = submit
selectItemValueDirectWrite = 0
selectItemValueEnumCodeProperty = 1
selectItemValueEnumCodeProperty codeOf = MALE
selectItemValueMap = 1
selectItemValueMap value = 大福
label = submit
selectItemValueDirectWrite = 0
selectItemValueEnumCodeProperty = 2
selectItemValueEnumCodeProperty codeOf = FEMALE
selectItemValueMap = 5
selectItemValueMap value = チョコ

さいごに

JSFのSelectItemは、今回のクラスで、Enumであれ、Mapであれ統一して処理できます。また、lombokのおかげで 可視化されていませんが of(List values) というメソッドも自動生成されています。
冗長的で使わないように思いますが、ファーストクラスコレクションとして統一した実装が出来るので用途が無いわけではないと思います。

今回はSubmitでMapのリストが変更するようにしました。
でも、リストの更新のためだけにForm全てをリクエストするのは性能面で好ましくないです。
次回はAjaxを使って、性能面にも配慮のある実装をしてみたいと思います。

*1:mike-neckのブログで「あぁ、そうかちゃんとしたいんだったら国際化対応すべきだ」と思い至り、とりあえずでも実装しておこうと思いました。

*2:ResourceBundleは、ちゃんとやろうとすると結構なボリュームになるので今後の課題にしたいと思います

*3:RDBの表現と同じものをmessage.propertiesにすべて準備する?いえいえ、それはそもそも設計から見直した方が良いように思います