How to write a custom converter for <p:pickList>
How can I write a custom converter when working with PrimeFaces components that use a list of POJO? My particular problem is with <p:pickList>
<p:pickList converter="????" value="#{bean.projects}" var="project"
itemLabel="#{project.开发者_StackOverflow社区name}" itemValue="#{project}">
Without a converter I get java.lang.ClassCastException
because JSF sets the submitted values with unconverted java.lang.String
submitted values.
It's possible, whithout other database access, but i don't know the best way. I use a very specific converter, works only for picklist. Try this:
@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;
}
and Picklist:
<p:pickList converter="primeFacesPickListConverter" value="#{bean.projects}" var="project"
itemLabel="#{project.name}" itemValue="#{project}">
Work's for me, improvements is necessary.
After research on how to write custom converter, here is the solution.
1. create a Java Class that implement 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. Register your custom converter in faces-config.xml
<converter>
<converter-id>projectConverter</converter-id>
<converter-class>org.xdrawing.converter.ProjectConverter</converter-class>
</converter>
3. So now inside Primefaces component, u just do converter="projectConverter"
. Note that projectConverter
is the <convert-id>
I just created. So to solve my problem above, I do this:
<p:pickList converter="projectConverter" value="#{bean.projects}" var="project"
itemLabel="#{project.name}" itemValue="#{project}">
Yes, you can write a converter that serializes/deserializes the objects in the picklist like this:
@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 "";
}
}
}
Then apply this converter on your picklist like this:
<p:pickList converter="PositionMetricConverter" value="#{bean.positionMetrics}"
var="positionMetric" itemLabel="#{positionMetric.name}" itemValue="#{positionMetric}"/>
and make sure your objects are serializable.
This problem is not primefaces related, just general JSF related.
Why should you hit the database again? Your bean already contains the list of Objects you want to display in a component, or is it request scoped?
- Create a Superclass for your Hibernate Pojo's containing an id field. If you don't want to create a super class just use the pojo class, but you need more converter classes.
- With that Superclass you can create a generic converter for all Pojo Classes containing a list of Pojo's passed in the constructor.
- Add the converter as a property in your session bean and use that converter in your JSF component.
If you access the final list via a get property in your bean or the nested one in the converter is of your choice.
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>
is there any way to implement that without 2 database hits?
I mean, when you have
#{bean.projects}
this is a database hit.
and when the converter puts
sBean.getProjectById(value);
is a unnecessary database hit, since bean.projects already have id and value of the objects
Yes, it's 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="#...
精彩评论