2016-03-25 3 views
3

私はHashMapメソッドを使用して、チェックボックスのリストをMap<String, Boolean>にバインドして成功させました。これは、動的な数のチェックボックスを持つことができるので、いいです。UISelectMany in ui:repeatにより、java.lang.ClassCastExceptionが発生します。[Ljava.lang.Object; java.util.Listにキャストすることができません

可変長リストselectManyMenuに拡張しようとしています。彼らはselectManyだから、私はMap<String, List<MyObject>>にバインドできるようにしたいと思います。私は単一の例をselectManyMenuList<MyObject>にバインドすることができますが、すべて正常に動作しますが、ホエイはselectManyMenusの動的数値をui:repeatに入れ、マップにバインドしようとすると奇妙な結果に終わります。値はデバッガによって検証され、toString()を呼び出すとマップに正しく格納されますが、マップの値がObjectで、List<MyObject>ではないと判断され、マップのキーにアクセスしようとするとClassCastExceptionsがスローされます。

JSFがバインディングのターゲットの実行時の型を決定する方法と関係していると思います。Mapの値にバインドしているため、型を取得する方法がわかりません。マップの値の型のパラメータ。おそらくMojarraにパッチを当てる以外の方法がありますか?

通常、selectManyMenusの動的番号を持つページを作成するにはどうすればよいですか?もちろん、Primefacesの<p:solveThisProblemForMe>コンポーネントを使用しないと、 (すべての深刻さで、Primefaceは私のコントロール外の要因のため、ここではオプションではありません)

質問UISelectMany on a List<T> causes java.lang.ClassCastException: java.lang.String cannot be cast to Tは私が気付いていない良い情報を持っていましたが、このSSCE :

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 

スタックトレースが切り取らが、この行に発生します。

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

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

答えて

4

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

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

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

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


更新:私は上記の理論をテストしている必要があります。 collectionTypeの背後にあるコレクションが別のジェネリックコレクション/マップにラップされている場合、これはMojarraでは機能しません。 MojarraはUISelectManyの値自体がすでにjava.util.Collectionのインスタンスを表している場合にのみcollectionTypeをチェックします。ただし、Mapにラップされているため、その(raw)タイプはjava.lang.Objectになり、MojarraはチェックをスキップしてcollectionTypeとなります。

MyFacesはUISelectManyレンダラでこれでより良い仕事をしました。

私の知る限りクロサギ科のソースコードを検査として、CategoryString nameList<MyObject> selectedItems性質を持つカスタムオブジェクトですList<Category>Map<String, List<Long>>を交換するよりも、この他の方法で回避する方法はありません。実際には、これは実際にELの動的キーを持つMapの利点を殺しますが、それはそれです。 collectionTypeがここに不要であることに注意してください

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> 

は、ここで項目タイプ(ちょうどあなたのMyObjectでそれを代用)としてLongを使用してMCVEです。まだconverterのみが必要です。

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

+0

この作業の例はどこかありますか? collectionTypeを追加しても効果はありませんでした。私はまだ 'collectionType =" java.util.ArrayList "'と 'f:attribute'フォームで同じClassCastExceptionを取得しています。 – Matt

+0

右、これに関連するモハラの別のバグをヒットしました。 MyFacesで動作します。私はこれに後で戻ってきます。 – BalusC

+0

BalusCありがとうございます。もう一度私のUIを再設計する必要から私を救った。 – Matt

関連する問題