3

HashMapチェックボックスのリストMap<String, Boolean>を成功にバインドするメソッドを使用しました。チェックボックスの数を動的に設定できるため、これは便利です。

それを の可変長リストに拡張しようとしていますselectManyMenu。それらはselectManyであるため、にバインドできるようにしたいと思いMap<String, List<MyObject>>ます。selectManyMenuシングルを a にバインドできる単一の例がありList<MyObject>、すべて正常に動作しますが、selectManyMenus の動的な数を a 内に配置ui:repeatしてマップにバインドしようとすると、奇妙な結果になります。デバッガーと呼び出しによって検証されるように、値はマップに正しく格納されますtoString()が、ランタイムは、マップの値が型であり、型Objectでないと判断List<MyObject>し、マップのキーにアクセスしようとすると ClassCastExceptions をスローします。

JSFがバインディングのターゲットのランタイムタイプを決定する方法と関係があると思います.aの値にバインディングしているMapため、の値タイプパラメータからタイプを取得することはわかりません地図。おそらくMojarraにパッチを当てる以外に、これに対する回避策はありますか?

一般に、動的な数の selectManyMenus を含むページを作成するにはどうすればよいですか? もちろん、Primefaces の<p:solveThisProblemForMe>コンポーネントを使用せずに。(真剣に言うと、Primefaces は、私の制御の及ばない要因のため、ここではオプションではありません。)

List<T> の UISelectManyという質問により、java.lang.ClassCastException: java.lang.String を T にキャストすることはできません。

JSF:

  <ui:define name="content">
    <h:form>
      <ui:repeat value="#{testBean.itemCategories}" var="category">
        <h:selectManyMenu value="#{testBean.selectedItemMap[category]}">
          <f:selectItems value="#{testBean.availableItems}" var="item" itemValue="#{item}" itemLabel="#{item.name}"></f:selectItems>
          <f:converter binding="#{itemConverter}"></f:converter>
          <f:validator validatorId="test.itemValidator"></f:validator>
        </h:selectManyMenu>
      </ui:repeat>
      <h:commandButton value="Submit">
        <f:ajax listener="#{testBean.submitSelections}" execute="@form"></f:ajax>
      </h:commandButton>
    </h:form>
  </ui:define>

コンバータ:

@Named
public class ItemConverter implements Converter {

  @Inject
  ItemStore itemStore;

  @Override
  public Object getAsObject(FacesContext context, UIComponent component, String value) {
    return itemStore.getById(value);
  }

  @Override
  public String getAsString(FacesContext context, UIComponent component, Object value) {
    return Optional.of(value)
                   .filter(v -> Item.class.isInstance(v))
                   .map(v -> ((Item) v).getId())
                   .orElse(null);
  }
}

バッキング Bean:

@Data
@Slf4j
@Named
@ViewScoped
public class TestBean implements Serializable {

  private static final long serialVersionUID = 1L;

  @Inject
  ItemStore itemStore;

  List<Item> availableItems;

  List<String> itemCategories;

  Map<String, List<Item>> selectedItemMap = new HashMap<>();

  public void initialize() {
    log.debug("Initialized TestBean");

    availableItems = itemStore.getAllItems();

    itemCategories = new ArrayList<>();
    itemCategories.add("First Category");
    itemCategories.add("Second Category");
    itemCategories.add("Third Category");
  }

  public void submitSelections(AjaxBehaviorEvent event) {
    log.debug("Submitted Selections");

    selectedItemMap.entrySet().forEach(entry -> {
      String key = entry.getKey();
      List<Item> items = entry.getValue();

      log.debug("Key: {}", key);

      items.forEach(item -> {
        log.debug("   Value: {}", item);
      });

    });

  }

}

ItemStore には、ID フィールドによってアイテムにアクセスするための HashMap メソッドとデリゲート メソッドが含まれているだけです。

アイテム:

@Data
@Builder
public class Item {
  private String id;
  private String name;
  private String value;
}

ItemListValidator:

@FacesValidator("test.itemValidator")
public class ItemListValidator implements Validator {

  @Override
  public void validate(FacesContext context, UIComponent component, Object value) throws ValidatorException {
    if (List.class.isInstance(value)) {

      if (((List) value).size() < 1) {
        throw new ValidatorException(new FacesMessage(FacesMessage.SEVERITY_FATAL, "You must select at least 1 Admin Area", "You must select at least 1 Admin Area"));
      }
    }
  }

}

エラー:

java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to java.util.List

Stacktrace は切り取られましたが、次の行で発生します。

List<Item> items = entry.getValue();

ここで何が欠けていますか?

4

1 に答える 1

4

関連する質問UISelectMany on a List<T> cause java.lang.ClassCastException: java.lang.String cannot be cast to Tで示唆されているように、ジェネリック型引数は実行時に使用できません。言い換えれば、EL はあなたがMap<String, List<Item>>. EL が知っているのは、 があるということだけですMap。そのため、選択した値のコンバーターとコレクションのコレクション型を明示的に指定しない限り、JSF はString選択した値とコレクションのオブジェクト配列Object[]をデフォルトにします。[in[Ljava.lang.Objectは配列を示すことに注意してください。

コレクション型を のインスタンスにしたい場合、目的の具象実装の FQN で属性java.util.Listを指定する必要があります。collectionType

<h:selectManyMenu ... collectionType="java.util.ArrayList">

次に、JSF は、選択されたアイテムを埋めてモデルに入れるために、正しいコレクション タイプがインスタンス化されていることを確認します。このようなソリューションが使用されている関連する質問がありますが、別の理由があります: org.hibernate.LazyInitializationException at com.sun.faces.renderkit.html_basic.MenuRenderer.convertSelectManyValuesForModel


更新:上記の理論をテストする必要がありました。collectionType背後のコレクションが別の汎用コレクション/マップにラップされている場合、これは Mojarra では機能しません。Mojarra は、値自体がすでに のインスタンスを表しているcollectionTypeかどうかのみをチェックします。ただし、 でラップされているため、その (生の) タイプはになり、Mojarra は のチェックをスキップします。UISelectManyjava.util.CollectionMapjava.lang.ObjectcollectionType

MyFaces は、UISelectManyレンダラーでこれをうまく処理しました。

私が Mojarra のソース コードを調べた限りでは、とプロパティを持つカスタム オブジェクトでMap<String, List<Long>>あるに置き換える以外に、この問題を回避する方法はありません。確かに、これは EL で動的キーを持つ利点を実際に殺してしまいますが、それはそれです。List<Category>CategoryString nameList<MyObject> selectedItemsMap

アイテムタイプとして使用するMCVELongを次に示します(単にあなたのに置き換えてくださいMyObject):

private List<Category> categories;
private List<Long> availableItems;

@PostConstruct
public void init() {
    categories = Arrays.asList(new Category("one"), new Category("two"), new Category("three"));
    availableItems = Arrays.asList(1L, 2L, 3L, 4L, 5L);
}

public void submit() {
    categories.forEach(c -> {
        System.out.println("Name: " + c.getName());

        for (Long selectedItem : c.getSelectedItems()) {
            System.out.println("Selected item: " + selectedItem);
        }
    });

    // ...
}

public class Category {

    private String name;
    private List<Long> selectedItems;

    public Category(String name) {
        this.name = name;
    }

    // ...
}

<h:form>
    <ui:repeat value="#{bean.categories}" var="category">
        <h:selectManyMenu value="#{category.selectedItems}" converter="javax.faces.Long">
            <f:selectItems value="#{bean.availableItems}" />
        </h:selectManyMenu>
    </ui:repeat>
    <h:commandButton value="submit" action="#{bean.submit}">
        <f:ajax execute="@form" />
    </h:commandButton>
</h:form>

collectionTypeここでは不要であることに注意してください。のみconverterがまだ必要です。

具体selectedItemMap.entrySet().forEach(entry -> { String key ...; List<Item> items ...;})的な問題とは関係ありませんが、単純化でき、入力コンポーネントでのみ使用する場合は不要selectedItemMap.forEach((key, items) -> {})であることを指摘したいと思います。ItemListValidatorrequired="true"

于 2016-03-25T16:12:10.943 に答える