web-dev-qa-db-fra.com

Comment écrire un convertisseur personnalisé pour <p: pickList>

Comment puis-je écrire un convertisseur personnalisé lorsque je travaille avec des composants PrimeFaces qui utilisent une liste de POJO? Mon problème particulier est avec <p:pickList>

<p:pickList converter="????" value="#{bean.projects}" var="project" 
                             itemLabel="#{project.name}" itemValue="#{project}">

Sans convertisseur, j'obtiens Java.lang.ClassCastException Car JSF définit les valeurs soumises avec des valeurs soumises Java.lang.String Non converties.

22
Thang Pham

Après des recherches sur la façon d'écrire un convertisseur personnalisé, voici la solution.
1. créer une classe Java qui implémente javax.faces.convert.Converter;

public class ProjectConverter implements Converter{

   @EJB
   DocumentSBean sBean;

   public ProjectConverter(){
   }

   public Object getAsObject(FacesContext context, UIComponent component, String value){
     return sBean.getProjectById(value);
     //If u look below, I convert the object into a unique string, which is its id.
     //Therefore, I just need to write a method that query the object back from the 
     //database if given a id. getProjectById, is a method inside my Session Bean that
     //does what I just described
   }

   public String getAsString(FacesContext context, UIComponent component, Object value)     
   {
     return ((Project) value).getId().toString(); //--> convert to a unique string.
   }
}

2. Enregistrez votre convertisseur personnalisé dans faces-config.xml

<converter>
    <converter-id>projectConverter</converter-id>
    <converter-class>org.xdrawing.converter.ProjectConverter</converter-class>
</converter>

3. Alors maintenant, à l'intérieur du composant Primefaces, vous faites simplement converter="projectConverter". Notez que projectConverter est le <convert-id> Je viens de créer. Donc, pour résoudre mon problème ci-dessus, je fais ceci:

<p:pickList converter="projectConverter" value="#{bean.projects}" var="project" 
                            itemLabel="#{project.name}" itemValue="#{project}">
18
Thang Pham

C'est possible, sans autre accès à la base de données, mais je ne connais pas la meilleure façon. J'utilise un convertisseur très spécifique, ne fonctionne que pour la liste de sélection. Essaye ça:

@FacesConverter(value = "primeFacesPickListConverter")public class PrimeFacesPickListConverter implements Converter {
@Override
public Object getAsObject(FacesContext arg0, UIComponent arg1, String arg2) {
    Object ret = null;
    if (arg1 instanceof PickList) {
        Object dualList = ((PickList) arg1).getValue();
        DualListModel dl = (DualListModel) dualList;
        for (Object o : dl.getSource()) {
            String id = "" + ((Project) o).getId();
            if (arg2.equals(id)) {
                ret = o;
                break;
            }
        }
        if (ret == null)
            for (Object o : dl.getTarget()) {
                String id = "" + ((Project) o).getId();
                if (arg2.equals(id)) {
                    ret = o;
                    break;
                }
            }
    }
    return ret;
}

@Override
public String getAsString(FacesContext arg0, UIComponent arg1, Object arg2) {
    String str = "";
    if (arg2 instanceof Project) {
        str = "" + ((Project) arg2).getId();
    }
    return str;
}

et liste de sélection:

<p:pickList converter="primeFacesPickListConverter" value="#{bean.projects}" var="project" 
                        itemLabel="#{project.name}" itemValue="#{project}">

Le travail est pour moi, des améliorations sont nécessaires.

39
Braully Rocha

Oui, vous pouvez écrire un convertisseur qui sérialise/désérialise les objets dans la liste de sélection comme ceci:

@FacesConverter(value="PositionMetricConverter")
public class PositionMetricConverter implements Converter {

    private static final Logger log = Logger.getLogger(PositionMetricConverter.class.getName());

    @Override
    public Object getAsObject(FacesContext facesContext, UIComponent uiComponent, String value) {
        try {
            byte[] data = Base64.decodeBase64(value);
            ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(data));
            Object o = ois.readObject();
            ois.close();
            return o;
        } catch (Exception e) {
            log.log(Level.SEVERE, "Unable to decode PositionMetric!", e);
            return null;
        }
    }

    @Override
    public String getAsString(FacesContext facesContext, UIComponent uiComponent, Object value) {
        try {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(baos);
            oos.writeObject(value);
            oos.close();

            return Base64.encodeBase64String(baos.toByteArray());
        } catch (IOException e) {
            log.log(Level.SEVERE, "Unable to encode PositionMetric!", e);
            return "";
        }
    }

}

Ensuite, appliquez ce convertisseur sur votre liste de sélection comme ceci:

<p:pickList converter="PositionMetricConverter" value="#{bean.positionMetrics}" 
    var="positionMetric" itemLabel="#{positionMetric.name}" itemValue="#{positionMetric}"/>

et assurez-vous que vos objets sont sérialisables.

6
Rares Oltean

Ce problème n'est pas lié aux primefaces, mais uniquement au JSF général.

Pourquoi devriez-vous à nouveau accéder à la base de données? Votre bean contient déjà la liste des objets que vous souhaitez afficher dans un composant, ou s'agit-il d'une étendue de demande?

  • Créez une superclasse pour votre Hibernate Pojo contenant un champ id. Si vous ne voulez pas créer une super classe, utilisez simplement la classe pojo, mais vous avez besoin de plus de classes de conversion.
  • Avec cette Superclasse, vous pouvez créer un convertisseur générique pour toutes les classes Pojo contenant une liste des Pojo passés dans le constructeur.
  • Ajoutez le convertisseur en tant que propriété dans votre bean session et utilisez ce convertisseur dans votre composant JSF.

Si vous accédez à la liste finale via une propriété get dans votre bean ou celle imbriquée dans le convertisseur est de votre choix.

public class SuperPojo
{
    protected Integer id;
    //constructor & getter
}

public class PojoTest extends SuperPojo 
{
    private String label = null;    
    //constructor & getter
}

public class SuperPojoConverter<T extends SuperPojo> implements Converter
{
    private Collection<T> superPojos;

    public IdEntityConverter(Collection<T> superPojos)
    {
        this.superPojos = superPojos;
    }

    @Override
    public Object getAsObject(FacesContext context, UIComponent component, String value)
    {
        //catch exceptions and empty or  null value!
        final int intValue = Integer.parseInt(value);
        for(SuperPojo superPojo : this.superPojos)
            if(superPojo.getId().intValue() == intValue)
                return superPojo;

        return null;
    }

    @Override
    public String getAsString(FacesContext context, UIComponent component, Object value)
    {
        //catch null and instanceof
        return String.valueOf(((SuperPojo)value).getId().intValue());
    }

    public Collection<T> getSuperPojos()
    {
        return this.superPojos;
    }
}

public class Bean 
{
    private SuperPojoConverter<PojoTest> pojoTestConverter = null;

    public Bean()
    {
        final List<PojoTest> pojoTests = //get list from hibernate
        this.pojoTestConverter = new SuperPojoConverter<PojoTest>(pojoTests);
    }

    public SuperPojoConverter<PojoTest> getPojoTestConverter()
    {
        return this.pojoTestConverter;
    }
}


<h:selectOneMenu value="#{selPojoTest}" converter="#{bean.getPojoTestConverter}">
    <f:selectItems value="#{bean.getPojoTestConverter.getSuperPojos}" var="varPojoTest" itemLabel="#{varPojoTest.label}" itemValue="#{varPojoTest}"/>
</h:selectOneMenu>
3
djmj

existe-t-il un moyen de mettre en œuvre cela sans 2 hits de base de données?

Je veux dire, quand vous avez

#{bean.projects}

c'est un coup de base de données.

et quand le convertisseur met

sBean.getProjectById(value);

est un hit de base de données inutile, car bean.projects a déjà l'id et la valeur des objets

2
bluefoot

Oui c'est possible:

public class DocumentSBean sBean implements Serializable{

private List<Document> projects;
// projects methods...
// ...

public Converter getDocumentConverter(){
 return docConverter;
}

private Converter docConverter = new Converter() {

        @Override
        public Object getAsObject(FacesContext context, UIComponent component, String value) {
            return projects.stream().filter(p -> p.getName().equals(value)).findFirst().orElse(null);
        }

        @Override
        public String getAsString(FacesContext context, UIComponent component, Object value) {
            return (value != null)
                    ? ((Document) value).toString()
                    : null;
        }
    };
}
<p:pickList converter="#{sBean.documentConverter}" value="#...
1
user3158918