java - framework - Conversor de entidad JSF genérico
jsf primefaces (7)
Esta pregunta ya tiene una respuesta aquí:
Estoy escribiendo mi primera aplicación web Java EE 6 como ejercicio de aprendizaje. No estoy usando un framework, solo JPA 2.0, EJB 3.1 y JSF 2.0.
Tengo un Convertidor personalizado para convertir una entidad JPA almacenada en un componente SelectOne a una Entidad. Estoy usando InitialContext.lookup para obtener una referencia a Session Bean para encontrar la entidad relevante.
Me gustaría crear un convertidor de entidades genérico para no tener que crear un convertidor por entidad. Pensé que crearía una entidad abstracta y que todas las entidades la ampliarían. Luego, cree un convertidor personalizado para la entidad abstracta y úselo como convertidor para todas las entidades.
¿Suena sensato y / o practicable?
¿Tendría más sentido no tener una entidad abstracta, solo un convertidor que convierta cualquier entidad? En ese caso, no estoy seguro de cómo obtendría una referencia al Session Bean apropiado.
He incluido mi convertidor actual porque no estoy seguro de obtener una referencia a mi Session Bean de la manera más eficiente.
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());
}
}
Bueno, hoy tuve el mismo problema, y lo resolví creando un ConversionHelper genérico y usándolo en el convertidor. Para este propósito, tengo un EntityService que es un SLSB genérico que utilizo para realizar operaciones CRUD simples para cualquier tipo de entidad. Además, mis entidades implementan una interfaz PersistentEntity, que tiene los métodos getId y setId y yo los guardo con claves primarias simples. Eso es.
Al final, mi convertidor se ve así:
@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);
}
}
Y mi asistente de conversión se ve así:
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;
}
}
Ahora crear convertidores para entidades JPA simples es una cuestión de duplicar un convertidor y cambiar 3 parámetros.
Esto está funcionando bien para mí, pero no sé si es el mejor enfoque en términos de estilo y rendimiento. Cualquier consejo sería apreciado.
Estoy usando algo como esto:
@Named
public class EntityConverter implements Converter {
@Inject
private EntityManager entityManager;
@Inject
private ConversionService conversionService;
@Override
public Object getAsObject(FacesContext context, UIComponent component, String value) {
Class<?> entityType = component.getValueExpression("value").getType(context.getELContext());
Class<?> idType = this.entityManager.getMetamodel().entity(entityType).getIdType().getJavaType();
Object id = this.conversionService.convert(idType, value);
return this.entityManager.getReference(entityType, id); // find() is possible too
}
@Override
public String getAsString(FacesContext context, UIComponent component, Object value) {
Object id = this.entityManager.getEntityManagerFactory().getPersistenceUnitUtil().getIdentifier(value);
return this.conversionService.convert(String.class, id);
}
}
ConversionService
se define así (la implementación está fuera del alcance aquí):
public interface ConversionService {
<T> T convert(Class<T> targetType, Object source);
}
en la plantilla, use <f:converter binding="#{entityConverter}" />
.
Estoy usando el mapa de vista JSF 2.0:
@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;
}
}
Para completar la respuesta de Craig Ringer, puede usar el genérico org.jboss.seam.faces.conversion.ObjectConverter
del http://seamframework.org/Seam3/FacesModule .
Puede obtener el código aquí: https://github.com/seam/faces/blob/develop/impl/src/main/java/org/jboss/seam/faces/conversion/ObjectConverter.java
Utiliza 2 HashMap
s (uno se usa de forma inversa) y almacena sus objetos en la Conversation
.
Pruebe esto usando 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;
}
}
Use Seam Faces, proporciona una clase de convertidor que hace lo que quiere.
org.jboss.seam.faces.conversion.Converter
Si bien es un proyecto de JBoss, Seam 3 funciona bien con Glassfish 3.1 y versiones posteriores.
http://seamframework.org/Seam3/FacesModule
En 3.1 tiene un par de dependencias adicionales; ver http://blog.ringerc.id.au/2011/05/using-seam-3-with-glassfish-31.html
mi solución es la siguiente:
@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 "";
}
}
}
y en páginas:
<h:selectOneMenu id="x" value="#{controller.x}"
converter="#{entityConverterBuilderBean.createConverter(''com.test.model.TestEntity'')}">