jsf - Cómo escribir un convertidor personalizado para<p: pickList>
converter primefaces selectonemenu (6)
¿Cómo puedo escribir un convertidor personalizado cuando trabajo con componentes de PrimeFaces que usan una lista de POJO? Mi problema particular es con <p:pickList>
<p:pickList converter="????" value="#{bean.projects}" var="project"
itemLabel="#{project.name}" itemValue="#{project}">
Sin un convertidor obtengo java.lang.ClassCastException
porque JSF establece los valores enviados con los valores enviados java.lang.String
no convertidos.
¿Hay alguna manera de implementar eso sin 2 hits en la base de datos?
Quiero decir, cuando tienes
#{bean.projects}
esto es un hit de base de datos.
y cuando el convertidor pone
sBean.getProjectById(value);
es un hit innecesario de la base de datos, ya que bean.projects ya tiene el id y el valor de los objetos
Este problema no está relacionado con la prima, solo está relacionado con JSF.
¿Por qué debería golpear la base de datos otra vez? Su bean ya contiene la lista de Objetos que desea mostrar en un componente, o es un ámbito de solicitud?
- Cree una Superclase para su Hibernate Pojo que contenga un campo de identificación. Si no quieres crear una súper clase simplemente utiliza la clase pojo, pero necesitas más clases de conversión.
- Con esa Superclase, puede crear un convertidor genérico para todas las Clases de Pojo que contengan una lista de Pojo pasados en el constructor.
- Agregue el convertidor como una propiedad en su bean de sesión y use ese convertidor en su componente JSF.
Si accede a la lista final a través de una propiedad get en su bean o la anidada en el convertidor es de su elección.
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>
Es posible, sin acceso a otra base de datos, pero no sé la mejor manera. Utilizo un convertidor muy específico, funciona solo para la lista de selección. Prueba esto:
@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;
}
y lista de selección:
<p:pickList converter="primeFacesPickListConverter" value="#{bean.projects}" var="project"
itemLabel="#{project.name}" itemValue="#{project}">
El trabajo es para mí, las mejoras son necesarias.
Después de la investigación sobre cómo escribir un convertidor personalizado, aquí está la solución.
1. crea una clase Java que implementa 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. Registre su convertidor personalizado en faces-config.xml
<converter>
<converter-id>projectConverter</converter-id>
<converter-class>org.xdrawing.converter.ProjectConverter</converter-class>
</converter>
3. Ahora, dentro del componente Primefaces, simplemente haz converter="projectConverter"
. Tenga en cuenta que projectConverter
es la <convert-id>
que acabo de crear. Entonces, para resolver mi problema anterior, hago esto:
<p:pickList converter="projectConverter" value="#{bean.projects}" var="project"
itemLabel="#{project.name}" itemValue="#{project}">
Si es posible:
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="#...
Sí, puede escribir un convertidor que serialice / deserialice los objetos en la lista de selección de esta manera:
@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 "";
}
}
}
A continuación, aplique este convertidor en su lista de selección de esta manera:
<p:pickList converter="PositionMetricConverter" value="#{bean.positionMetrics}"
var="positionMetric" itemLabel="#{positionMetric.name}" itemValue="#{positionMetric}"/>
y asegúrese de que sus objetos sean serializables.