web-dev-qa-db-fra.com

Convertisseur d'entité JSF générique

J'écris ma première application Web Java EE 6 en tant qu'exercice d'apprentissage. Je n'utilise pas de framework, mais uniquement JPA 2.0, EJB 3.1 et JSF 2.0. 

J'ai un convertisseur personnalisé pour convertir une entité JPA stockée dans un composant SelectOne en une entité. J'utilise un InitialContext.lookup pour obtenir une référence à un bean session afin de trouver l'entité appropriée. 

J'aimerais créer un convertisseur d'entité générique afin d'éviter de créer un convertisseur par entité. Je pensais que je créerais une entité abstraite et que toutes les entités l'étendraient. Créez ensuite un convertisseur personnalisé pour l'entité abstraite et utilisez-le comme convertisseur pour toutes les entités. 

Cela vous semble-t-il raisonnable et/ou réalisable? 

Serait-il plus logique de ne pas avoir une entité abstraite, mais simplement un convertisseur qui convertit n'importe quelle entité? Dans ce cas, je ne sais pas comment obtenir une référence au bean session approprié.

J'ai inclus mon convertisseur actuel car je ne suis pas sûr d'obtenir une référence à mon Session Bean de la manière la plus efficace.

package com.mycom.rentalstore.converters;

import com.mycom.rentalstore.ejbs.ClassificationEJB;
import com.mycom.rentalstore.entities.Classification;
import javax.faces.application.FacesMessage;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.convert.Converter;
import javax.faces.convert.ConverterException;
import javax.faces.convert.FacesConverter;
import javax.naming.InitialContext;
import javax.naming.NamingException;

@FacesConverter(forClass = Classification.class)
public class ClassificationConverter implements Converter {

    private InitialContext ic;
    private ClassificationEJB classificationEJB;

    @Override
    public Object getAsObject(FacesContext context, UIComponent component, String value) {

        try {
            ic = new InitialContext();
            classificationEJB = (ClassificationEJB) ic.lookup("Java:global/com.mycom.rentalstore_RentalStore_war_1.0-SNAPSHOT/ClassificationEJB");

        } catch (NamingException e) {
            throw new ConverterException(new FacesMessage(String.format("Cannot obtain InitialContext - %s", e)), e);
        }

        try {
            return classificationEJB.getClassificationById(Long.valueOf(value));
        } catch (Exception e) {
            throw new ConverterException(new FacesMessage(String.format("Cannot convert %s to Classification - %s", value, e)), e);
        }
    }

    @Override
    public String getAsString(FacesContext context, UIComponent component, Object value) {
        return String.valueOf(((Classification) value).getId());
    }
}
26
retrodev

Eh bien, j’ai eu le même problème aujourd’hui, et je l’ai résolu en créant un ConversionHelper générique et en l’utilisant dans le convertisseur. À cette fin, j’ai un EntityService qui est un SLSB générique que j’utilise pour effectuer des opérations CRUD simples. n'importe quel type d'entité. De plus, mes entités implémentent une interface PersistentEntity, qui comporte les méthodes getId et setId, que je conserve avec des clés primaires simples. C'est tout.

En fin de compte, mon convertisseur ressemble à ceci:


@FacesConverter(value = "userConverter", forClass = User.class)
public class UserConverter implements Converter {

    @Override
    public Object getAsObject(FacesContext ctx, UIComponent component, Java.lang.String value) {

        return ConversionHelper.getAsObject(User.class, value);
    }

    @Override
    public String getAsString(FacesContext ctx, UIComponent component, Object value) {

        return ConversionHelper.getAsString(value);
    }
}

Et mon assistant de conversion ressemble à ceci:


public final class ConversionHelper {

    private ConversionHelper() {
    }

    public static <T> T getAsObject(Class<T> returnType, String value) {

        if (returnType== null) {

            throw new NullPointerException("Trying to getAsObject with a null return type.");
        }

        if (value == null) {

            throw new NullPointerException("Trying to getAsObject with a null value.");
        }

        Long id = null;

        try {

            id = Long.parseLong(value);

        } catch (NumberFormatException e) {

            throw new ConverterException("Trying to getAsObject with a wrong id format.");
        }

        try {

            Context initialContext = new InitialContext();
            EntityService entityService = (EntityService) initialContext.lookup("Java:global/myapp/EntityService");

            T result = (T) entityService.find(returnType, id);

            return result;

        } catch (NamingException e) {

            throw new ConverterException("EntityService not found.");
        }
    }

    public static String getAsString(Object value) {

        if (value instanceof PersistentEntity) {

            PersistentEntity result = (PersistentEntity) value;

            return String.valueOf(result.getId());
        }

        return null;
    }
}

Maintenant, créer des convertisseurs pour des entités JPA simples consiste à dupliquer un convertisseur et à modifier 3 paramètres.

Cela fonctionne bien pour moi, mais je ne sais pas si c'est la meilleure approche en termes de style et de performance. Des conseils seraient appréciés.

7
rbento

J'utilise JSF 2.0 voir la carte:

@FacesConverter("entityConverter")
public class EntityConverter implements Converter {

private static final String key = "com.example.jsf.EntityConverter";
private static final String empty = "";

private Map<String, Object> getViewMap(FacesContext context) {
    Map<String, Object> viewMap = context.getViewRoot().getViewMap();
    @SuppressWarnings({ "unchecked", "rawtypes" })
    Map<String, Object> idMap = (Map) viewMap.get(key);
    if (idMap == null) {
        idMap = new HashMap<String, Object>();
        viewMap.put(key, idMap);
    }
    return idMap;
}

@Override
public Object getAsObject(FacesContext context, UIComponent c, String value) {
    if (value.isEmpty()) {
        return null;
    }
    return getViewMap(context).get(value);
}

@Override
public String getAsString(FacesContext context, UIComponent c, Object value) {
    if (value == null) {
        return empty;
    }
    String id = ((Persistent) value).getId().toString();
    getViewMap(context).put(id, value);
    return id;
}
}
7
Ecmel Ercan

ma solution est la suivante:

@ManagedBean
@SessionScoped
public class EntityConverterBuilderBean {
    private static Logger logger = LoggerFactory.getLogger(EntityConverterBuilderBean.class);
    @EJB
    private GenericDao dao;

    public GenericConverter createConverter(String entityClass) {
        return new GenericConverter(entityClass, dao);
    }


}

public class GenericConverter implements Converter {

    private Class clazz;
    private GenericDao dao;

    public GenericConverter(String clazz, Generic dao) {
        try {
            this.clazz = Class.forName(clazz);
            this.dao = dao;
        } catch (Exception e) {
            logger.error("cannot get class: " + clazz, e);
            throw new RuntimeException(e);
        }
    }

    public Object getAsObject(javax.faces.context.FacesContext facesContext, javax.faces.component.UIComponent uiComponent, Java.lang.String s) {
        Object ret = null;

        if (!"".equals(s)) {
            Long id = new Long(s);
            ret = dao.findById(clazz, id);
        }

        return ret;
    }

    public String getAsString(javax.faces.context.FacesContext facesContext, javax.faces.component.UIComponent uiComponent, Java.lang.Object o) {
        if (o != null) {
            return ((SimpleEntity) o).getId() + "";
        } else {
            return "";
        }
    }
}

et dans les pages:

 <h:selectOneMenu id="x" value="#{controller.x}" 
      converter="#{entityConverterBuilderBean.createConverter('com.test.model.TestEntity')}">
2
alexandrubarbat

Essayez ceci avec Seam Faces de Seam 3.

@Named("DocTypeConverter")
public class DocumentTypeConverter implements Converter, Serializable {

    private static final long serialVersionUID = 1L;

    @Inject
    private DocumentTypeSessionEJB proDocTypeSb;

    @Override
    public Object getAsObject(FacesContext context, UIComponent component,
            String value) {
        DocumentType result = null;

        if (value != null && !value.trim().equals("")) {  
            try {  
                result = (DocumentType) proDocTypeSb.findById(DocumentType.class, value);
            } catch(Exception exception) {  
                throw new ConverterException(new FacesMessage(FacesMessage.SEVERITY_ERROR, "Conversion Error", "Not a valid value"));  
            }  
        }  

        return result;
    }

    @Override
    public String getAsString(FacesContext context, UIComponent component,
            Object value) {
        String result = null;
        if (value != null && value instanceof DocumentType){
            DocumentType docType = (DocumentType) value;
            result = docType.getId();
        }

        return result;
    }

}
1
ichalos

Utilisez Seam Faces, il fournit une classe Converter qui fait ce que vous voulez.

org.jboss.seam.faces.conversion.Converter

Bien que ce soit un projet JBoss, Seam 3 fonctionne bien avec Glassfish 3.1 et plus récent.

http://seamframework.org/Seam3/FacesModule

Sur 3.1, il y a quelques dépendances supplémentaires; voir http://blog.ringerc.id.au/2011/05/using-seam-3-with-glassfish-31.html

1
Craig Ringer

(MISE À JOUR POUR JSF 2.3)

J'utilise quelque chose comme ça:

@FacesConverter(value = "entityConverter", managed = true)
public class EntityConverter implements Converter<Object> {

    @Inject
    private EntityManager entityManager;

    @Override
    public Object getAsObject(FacesContext context, UIComponent component, String value) {
        Class<?> entityType = component.getValueExpression("value").getType(context.getELContext());
        Class<?> idType = entityManager.getMetamodel().entity(entityType).getIdType().getJavaType();
        Converter idConverter = context.getApplication().createConverter(idType);
        Object id = idConverter.getAsObject(context, component, value);
        return entityManager.getReference(entityType, id);
    }

    @Override
    public String getAsString(FacesContext context, UIComponent component, Object value) {
        Object id = entityManager.getEntityManagerFactory().getPersistenceUnitUtil().getIdentifier(value);
        Converter idConverter = context.getApplication().createConverter(id.getClass());
        return idConverter.getAsString(context, component, id);
    }
}

Dans le modèle, utilisez <f:converter binding="#{entityConverter}" />.

1
Xavier Dury

Pour compléter la réponse de Craig Ringer, vous pouvez utiliser le nom générique org.jboss.seam.faces.conversion.ObjectConverter du Seam 3 FacesModule .

Vous pouvez récupérer le code ici: https://github.com/seam/faces/blob/develop/impl/src/main/Java/org/jboss/seam/faces/conversion/ObjectConverter.Java

Il utilise 2 HashMaps (un est utilisé à l'envers) et stocke ses objets dans Conversation.

0
Anthony O.