SelectOneMenu always shows first item not the selected - Primefaces - jsf

Some help would be appreciated on this issue.
I have several JSF Pages, but one of them is a Subcategory where there is a SelectOneMenu for choosing the Category, but when I try to edit the subcategory, this SelectOneMenu always shows the first value and is not getting preselected.
How could I solve this. I have read a lot of SO posts and eventhougn I have implemented a couple of advice I have not achieved it. for example, a #BalusC's one:
primefaces selectOneMenu doesn't working when it should
Here is the View
<h:outputText value="Subcategory ID : #{subcategoryController.subcategory.subcategoryId}"/>
<p:selectOneMenu id="cboCategoryDialog" converter="subcategoryConverter"
value="#{subcategoryController.category.categoryId}"
style="width: 100%"
<f:selectItems value="#{subcategoryController.categoryList}"
var="subcat"
itemLabel="#{subcat.categoryName}"
itemValue="#{subcat.categoryId}"/>
</p:selectOneMenu>**
This is the Subcategory Entity:
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#Column(name = "subcategory_id")
private Integer subcategoryId;
#Basic(optional = false)
#NotNull
#Size(min = 1, max = 45)
#Column(name = "subcategory_name")
private String subcategoryName;
#JoinColumn(name = "category_id", referencedColumnName = "category_id")
#OneToOne(optional = false;
private Category categoryId;
//Getters and Setters Methods
#Override
public int hashCode() {
return (subcategoryId != null)
? (this.getClass().hashCode() + subcategoryId.hashCode())
: super.hashCode();
}
#Override
public boolean equals(Object obj) {
return (obj instanceof Subcategory) && (subcategoryId != null)
? subcategoryId.equals(((Subcategory) obj).subcategoryId)
: (obj == this);
}
SubcategoryConverter
#RequestScoped
#FacesConverter("subcategoryConverter")
public class SubcategoryConverter implements Converter {
#EJB
private SubcategoryFacadeLocal EJBsubcategory;
#Override
public String getAsString(FacesContext context, UIComponent component, Object value) {
if (!(value instanceof Subcategory) || ((Subcategory) value).getSubcategoryId() == null) {
return null;
}
return String.valueOf(((Subcategory) value).getSubcategoryId());
}
#Override
public Object getAsObject(FacesContext context, UIComponent component, String value) {
if (value == null || !value.matches("\\d+")) {
return null;
}
Subcategory subcategory = EJBsubcategory.find(Integer.valueOf(value));
if (subcategory == null) {
throw new ConverterException(new FacesMessage("Unknown operation ID: " + value));
}
return subcategory;
}
}
Below image shows the Dialog when I try to edit any item, it always shows computer because its the first itm.
See the image showing the issue
I have been reading some related useful questions about this, but I have not been able to fix it.
Thanks in advance,

After reviewing the code, I realized that I was using the wrong entity in the JSF.:
For example:
In the question, I posted: subcategoryController.category.categoryId to save Category object in a subcategory object.
As follow:
<p:selectOneMenu id="cboCategoryDialog" converter="subcategoryConverter"
value="#{subcategoryController.category.categoryId}"
But, the correct approach is the following:
<p:selectOneMenu id="cboCategoryDialog" converter="omnifaces.SelectItemsConverter"
value="#{subcategoryController.subcategory.categoryId}"
Now, this component is "pointing" to the correct object.
Another issue that I was facing is:
As I have nested entities, example: Entity.entity.entity they should be inicialized in every level, before using it.
Some posts that really, really, really helped me on this:
On solving the nested entities issues (PropertyNotFoundException).
Identifying and solving javax.el.PropertyNotFoundException: Target Unreachable
On using normal and OmniFaces Converter on SelectOneMenu:
http://balusc.omnifaces.org/2007/09/objects-in-hselectonemenu.html

Be sure that your entity hashCode() and equals() methods are implemented and doesn't try to access nullable fields. That worked for me!

Related

custom converter - dependent selectonemenu not populated

I have tried to set up two selectOneMenus where the first selects an object, and in the second, one of five object parameters can be selected. For this, I have built a custom converter, and "id" should be passed from the first selectOneMenu to uniqely identify the object for the second menu. On page load, the getAsString method is called and iterated through all items of the first menu. On selecting an item, getAsObject is called, and the correct id is used and the correct Question is returned. But: the second menu is not populated at all. What did I miss? Help appreciated, I'm still learning this.
Here is the first selectOneMenu:
<h:selectOneMenu converter="answerDeliverer">
<f:selectItems value="#{questionBean.list}" var="question" itemValue="#{question}" itemLabel="#{question.text}"/>
<f:ajax event="change" render="answers"></f:ajax>
</h:selectOneMenu>
And the second:
<h:selectOneMenu id="answers">
<f:selectItem itemValue="1" itemLabel="#{question.a1}"/>
<f:selectItem itemValue="2" itemLabel="#{question.a2}"/>
<f:selectItem itemValue="3" itemLabel="#{question.a3}"/>
<f:selectItem itemValue="4" itemLabel="#{question.a4}"/>
<f:selectItem itemValue="5" itemLabel="#{question.a5}"/>
</h:selectOneMenu>
And the converter:
#ManagedBean (name = "answerDeliverer")
#RequestScoped
public class answerDeliverer implements Converter {
#EJB
private QuestionDAO questionDAO;
#Override
public Object getAsObject(FacesContext facesContext, UIComponent uiComponent, String s) {
return questionDAO.get(Integer.parseInt(s));
}
#Override
public String getAsString(FacesContext facesContext, UIComponent uiComponent, Object o) {
if (o instanceof Question) {
return ((Question) o).getId();
} else if (o instanceof String) {
return ((String) o);
} else throw new ConverterException(new FacesMessage(o + " is not a valid Question or id"));
//just so I can try either question or question.id as itemValues
}
}
The error log is empty, only on startup I get several warnings of this type:
Unknow type constant pool 18 at position 7
Nov 03, 2015 1:17:10 PM com.sun.faces.config.JavaClassScanningAnnotationScanner$ConstantPoolInfo containsAnnotation
After hours and hours of debugging, I'm stuck now. I can conclude the following:
The answerDeliverer seems to work. On selection, getAsObject fetches the correct question from the DB
XHR is registered in browser debug mode on every selection with a "200 OK" server answer
There is no other error in the log
I seem to have something wrong with bringing the question into the scope of the second selectOneMenu. Question is not a ManagedBean, and in my QuestionBean, private Question question = new Question(), so, could it be that no matter what I do in the first selectOneMenu, the second always generates a new instance and doesn't care about what I did before? And thus erases any question I had before? How could I avoid that?
I'm using Mojarra-2.2.1 on Tomcat 8. Also tried to go with OmniFaces, but got stuck in an infinite loop of debugging the setup there before giving up on that.
I'll add my Question POJO and the Bean if that helps any.
public class Question implements Serializable {
private String text;
private String a1 = Constants.defaulta1;
private String a2 = Constants.defaulta2;
private String a3 = Constants.defaulta3;
private String a4 = Constants.defaulta4;
private String a5 = Constants.defaulta5;
private String id;
private String nodeid;
//getters and setters
#Override
public boolean equals(Object other) {
return (other != null && getClass() == other.getClass() && id != null)
? id.equals(((Question) other).id)
: (other == this);
}
#Override
public int hashCode() {
return (id != null)
? (getClass().hashCode() + id.hashCode())
: super.hashCode();
}
#Override
public String toString() {
return id;
}
}
and the QuestionBean:
#ManagedBean (name="questionBean")
#ViewScoped
public class QuestionBean implements Serializable {
private List<Question> list;
//this was for a CRUD page; I deleted the other irrelevant code
private Question question = new Question();
private boolean edited;
#EJB
private QuestionDAO questionDAO;
#PostConstruct
public void init() {
list = questionDAO.getAll();
}
public List<Question> getList() {
return list;
}
public Question getQuestion() {
return question;
}
}

<f:selectItems> only shows toString() of the model as item label

I am facing a problem to populate SelectOneMenu correctly using converter. I followed several google references and lastly http://balusc.blogspot.in/2007/09/objects-in-hselectonemenu.html link of BalusC. But could not figure out the problem. I am giving all my entity, managedbean and jsf code for your consideration.
Entity:
#Entity
#Table(name = "gender")
#XmlRootElement
#NamedQueries({
#NamedQuery(name = "Gender.findAll", query = "SELECT g FROM Gender g"),
#NamedQuery(name = "Gender.findById", query = "SELECT g FROM Gender g WHERE g.id = :id"),
#NamedQuery(name = "Gender.findByGender", query = "SELECT g FROM Gender g WHERE g.gender = :gender")})
public class Gender implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Basic(optional = false)
#NotNull
#Column(name = "id")
private Short id;
#Size(max = 10)
#Column(name = "gender")
private String gender;
#OneToMany(mappedBy = "gender")
private List<Patient> patientList;
public Gender() {
}
public Gender(Short id) {
this.id = id;
}
public Short getId() {
return id;
}
public void setId(Short id) {
this.id = id;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
#XmlTransient
public List<Patient> getPatientList() {
return patientList;
}
public void setPatientList(List<Patient> patientList) {
this.patientList = patientList;
}
#Override
public int hashCode() {
return (id != null) ? (getClass().hashCode() + id.hashCode()) : super.hashCode();
}
#Override
public boolean equals(Object object) {
// TODO: Warning - this method won't work in the case the id fields are not set
return (object instanceof Gender) && (id != null) ? id.equals(((Gender) object).id) : (object == this);
}
#Override
public String toString() {
return "hms.models.Gender[ id=" + id + " ]";
}
Converter:
#FacesConverter(value = "genderConverter")
public class GenderConverter implements Converter {
#Inject GenderService gs;
#Override
public Object getAsObject(FacesContext context, UIComponent component, String value) {
if(value == null){
return null;
}
return gs.getGender(Integer.parseInt(value));
}
#Override
public String getAsString(FacesContext context, UIComponent component, Object value) {
if(value == null) {
return null;
}
if (!(value instanceof Gender)) {
throw new ConverterException("The value is not a valid Gender: " + value);
}
return ((Gender) value).getGender();
}
}
And JSF SelectOneMenu tag:
<h:selectOneMenu id="gender" value="#{registrationBean.patient.gender}" converter="genderConverter">
<f:selectItem itemValue="0" itemLabel="-- Select Gender --" />
<f:selectItems value="#{registrationBean.genderList}" />
</h:selectOneMenu>
The dropdown output is similar to "hms.models.Gender[id=1]" and so on. Image sample has given below:
Can anybody help? Thanks in advance.
Your problem is two-fold.
Firstly, in the output the select item label is being presented, not the select item value. The converter only affects the select item value, not the select item label. Unless otherwise specified via itemLabel attribute, the select item label defaults to toString() outcome of the select item value.
Assuming that you want to display the gender property as select item label, then this should do:
<f:selectItems value="#{registrationBean.genderList}" var="gender"
itemValue="#{gender}" itemLabel="#{gender.gender}" />
(it should be said that having a property with the same name as the class itself is quite awkward)
That should fix the presentation fail.
Secondly, you're for the output converting from Gender instance to its gender property. However, you're for the input attempting to interpret the outputted gender property as an id in order to find the associated Gender instance. This doesn't make any sense. Fix the converter accordingly to give the id back in getAsString(), exactly as expected by getAsObject().
return ((Gender) value).getId().toString();
That should fix the form submit fail.
See also:
How to populate options of h:selectOneMenu from database?

How to use detached entitys + version in a JSF Converter

i have a problem with conversion of entitys with a version. I made a simple example to explain my problem because the "real" application is to big and contains many unnecessary things.
Situation: I have a web application with primefaces and openjpa. I have 20 components (autocompletes + selectedmenues) that needs a converter and they use persistence entitys.
Informations: I only want use jsf,primefaces for it! (Nothing special like omnifaces or something else.) The Question is at bottom. This is only test-code. It is NOT complete and there are some strange things. But this explain my problem at best.
Example entity: (Only fields and hashcode + equals)
#Entity
public class Person {
#Id
private Long id;
private String name;
#Version
private Long version;
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Person other = (Person) obj;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
}
First solution: My first solution was that i make a own converter for every component.I inject my managed bean there and use the getter from the "value" of the component.
Bean
#ManagedBean(name = "personBean")
#ViewScoped
public class PersonBean implements Serializable {
private List<Person> persons;
/** unnecessary things **/
xhtml:
<p:selectOneMenu >
<f:selectItems value="#{personBean.persons}"/>
</p:selectOneMenu>
converter:
#ManagedBean
#RequestScoped
public class PersonConverter implements Converter{
#ManagedProperty(value = "personBean")
private PersonBean personBean;
#Override
public Object getAsObject(FacesContext context, UIComponent component,
String value) {
//null & empty checks
Long id = Long.valueOf(value);
for(Person person : personBean.getPersons()){
if(person.getId().equals(id)){
return person;
}
}
throw new ConverterException("some text");
}
#Override
public String getAsString(FacesContext context, UIComponent component,
Object value) {
//null & Instanceof checks
return String.valueOf(((Person)value).getId());
}
}
Summary: This solution works good. But i found that there must be a better solution as an converter for every component.
Second solution: I found here on stackoverflow the Global Entity Converter. One converter for all, i thought that was a good solution. ("p:autocomplete for a global Entity Converter"). I use it and i thought it works fine. BUT after a few tests i found another big problem, the version of the entity.
Problem1 with entity converter:
I have the version field not in my hashcode or equals (i found nothing about it). I only read this (The JPA hashCode() / equals() dilemma) about it. The problem is that the entity will not replaced in the hashmap and in some cases i get an optimistic locking exception because the "old" entity stays in the hashmap.
if (!entities.containsKey(entity)) {
String uuid = UUID.randomUUID().toString();
entities.put(entity, uuid);
return uuid;
} else {
return entities.get(entity);
}
Solution: I thought that i can resolve this problem by adding an interface to my entitys that checks whether a version exists.
Interface:
public interface EntityVersionCheck {
public boolean hasVersion();
}
Implementation:
#Override
public boolean hasVersion() {
return true;
}
Converter:
#Override
public String getAsString(FacesContext context, UIComponent component, Object entity) {
synchronized (entities) {
if(entity instanceof EntityVersionCheck && ((EntityVersionCheck)entity).hasVersion()){
entities.remove(entity);
}
if (!entities.containsKey(entity)) {
String uuid = UUID.randomUUID().toString();
entities.put(entity, uuid);
return uuid;
} else {
return entities.get(entity);
}
}
}
This solution works for the optimistic locking exception but brings another problem!
Problem2 with entity converter:
<p:selectOneMenu value="#{organisation.leader}">
<f:selectItems value="#{personBean.persons}"/>
</p:selectOneMenu>
If a organisation has already a leader. It will be replaced with a new uuid if the leader is in the persons - list, too. The leader will be set to null or convert exception because the uuid does not exists anymore in the hashmap. It means he use the converter for the organisation.leader and add the leader to the hashmap. Than comes the persons- List and add all other persons in a hashmap and override the uuid from the organisation.leader if he exists in persons, too.
Here are two cases now:
When i select a other leader, it works normally.
If i dont change the "current" selection and submit the organisation.leader tries to find his "old" uuid but the other person from the person list has override it and the uuid does not exists and the organisation.leader is null.
I found another solution for it and this is my final solution BUT i find, that is a very very strange solution and i will do this better but i found nothing about it.
Final Solution
I add the "old" uuid to the "new" object.
#Override
public String getAsString(FacesContext context, UIComponent component,
Object entity) {
synchronized (entities) {
String currentuuid = null;
if (entity instanceof EntityVersionCheck
&& ((EntityVersionCheck) entity).hasVersion()) {
currentuuid = entities.get(entity);
entities.remove(entity);
}
if (!entities.containsKey(entity)) {
if (currentuuid == null) {
currentuuid = UUID.randomUUID().toString();
}
entities.put(entity, currentuuid);
return currentuuid;
} else {
return entities.get(entity);
}
}
}
Question: How i make this better and right?
If Solution 1 worked and you just want it more generic:
Holding your instances within scope of a bean you can use a more generic converter by removing the managed-bean lookup from it. Your entities should inherit from a base entity with a identifier property. You can instantiate this converter in your bean where you retrieved the entities.
Or use a guid map or public identifier if id should not be exposed in html source.
#ManagedBean(name = "personBean")
#ViewScoped
public class PersonBean implements Serializable {
private List<Person> persons;
private EntityConverter<Person> converter;
// this.converter = new EntityConverter<>(persons);
}
<p:selectOneMenu converter="#{personBean.converter}">
<f:selectItems value="#{personBean.persons}"/>
</p:selectOneMenu>
Abstract Base Entity converter:
/**
* Abstract Entity Object JSF Converter which by default converts by {#link Entity#getId()}
*/
public abstract class AEntityConverter<T extends Entity> implements Converter
{
#Override
public String getAsString(final FacesContext context, final UIComponent component, final Object value)
{
if (value instanceof Entity)
{
final Entity entity = (Entity) value;
if (entity.getId() != null)
return String.valueOf(entity.getId());
}
return null;
}
}
Cached collection:
/**
* Entity JSF Converter which holds a Collection of Entities
*/
public class EntityConverter<T extends Entity> extends AEntityConverter<T>
{
/**
* Collection of Entity Objects
*/
protected Collection<T> entities;
/**
* Creates a new Entity Converter with the given Entity Object's
*
* #param entities Collection of Entity's
*/
public EntityConverter(final Collection<T> entities)
{
this.entities = entities;
}
#Override
public Object getAsObject(final FacesContext context, final UIComponent component, final String value)
{
if (value == null || value.trim().equals(""))
return null;
try
{
final int id = Integer.parseInt(value);
for (final Entity entity : this.entities)
if (entity.getId().intValue() == id)
return entity;
}
catch (final RuntimeException e)
{
// do something --> redirect to exception site
}
return null;
}
#Override
public void setEntities(final Collection<T> entities)
{
this.entities = entities;
}
#Override
public Collection<T> getEntities()
{
return this.entities;
}
}
Remote or database lookup Converter:
/**
* Entity Object JSF Converter
*/
public class EntityRemoteConverter<T extends Entity> extends AEntityConverter<T>
{
/**
* Dao
*/
protected final Dao<T> dao;
public EntityRemoteConverter(final EntityDao<T> dao)
{
this.dao = dao;
}
#Override
public Object getAsObject(final FacesContext context, final UIComponent component, final String value)
{
// check for changed value
// no need to hit database or remote server if value did not changed!
if (value == null || value.trim().equals(""))
return null;
try
{
final int id = Integer.parseInt(value);
return this.dao.getEntity(id);
}
catch (final RuntimeException e)
{
// do someting
}
return null;
}
}
I use dao approach whenever I have to convert view-parameters and bean was not constructed yet.
Avoid expensive lookups
In dao approach you should check if the value has changed before doing potential expensive lookups, since converters could be called multiple times within different phases.
Have a look at source of: http://showcase.omnifaces.org/converters/ValueChangeConverter
This basic approach is very flexible and can be extended easily for many use cases.

rich:pickList & selectManyListbox - JSF Converter Validation Error: Value not valid [duplicate]

This question already has answers here:
Validation Error: Value is not valid
(3 answers)
Closed 7 years ago.
I replaced rich:pickList to selectManyListbox for testing, when submit values to server it still displays error: "Validation Error: Value is not valid". I also set breakpoint to debug StaffConverter (getAsobject), but system never invokes it. Pls let me know some reasons why my converter never invoked and suggest me how to fix this. thanks
xhtml file:
<h:selectManyListbox value="#{reportController.selectedStaffList}" converter="staffConverter">
<f:selectItems value="#{reportController.staffList}"
var="item" itemValue="#{item}" itemLabel="#{item.name}" />
</h:selectManyListbox>
<rich:pickList value="#{reportController.selectedStaffList}" converter="staffConverter" sourceCaption="Available flight" targetCaption="Selected flight" listWidth="195px" listHeight="100px" orderable="true">
<f:selectItems value="#{reportController.staffList}" var="item" itemValue="#{item}" itemLabel="#{item.name}" />
</rich:pickList>
My Converter:
#FacesConverter(forClass = Staff.class)
public static class StaffConverter implements Converter {
public Object getAsObject(FacesContext facesContext, UIComponent component, String value) {
if (value == null || value.length() == 0) {
return null;
}
StaffController controller = StaffController.getInstance();
return controller.facade.find(Staff.class, getKey(value));
}
java.lang.Integer getKey(String value) {
java.lang.Integer key;
key = Integer.valueOf(value);
return key;
}
String getStringKey(java.lang.Integer value) {
StringBuffer sb = new StringBuffer();
sb.append(value);
return sb.toString();
}
public String getAsString(FacesContext facesContext, UIComponent component, Object object) {
if (object == null) {
return null;
}
if (object instanceof Staff) {
Staff o = (Staff) object;
return getStringKey(o.getStaffCode());
} else {
throw new IllegalArgumentException("object " + object + " is of type " + object.getClass().getName()
+ "; expected type: " + StaffController.class.getName());
}
}
}
I implemented equals method in Staff:
#Override
public boolean equals(Object object) {
if (!(object instanceof Staff)) {
return false;
}
Staff other = (Staff) object;
return (this.staffCode == other.staffCode);
}
Just to have it posted somewhere for people - like me - who like searching for solutions for development issues on google or stackoverflow...
I had this issue several times, depending on which kind of pojos I was using in the converters... Finally I think I found an elegant solution.
In my case I use JPA entity classes directly as I wanted to save the DTO layer. Well, with some entities the rich:pickList worked, with others not... I also tracked it down to the equals method. In the example below for instance it did not work for the userGroupConverter bean.
My solution is simply to inline overwrite the equals method, so the one from the entity (I often use lombok) is untouched and does not need to be changed, at all! So in my converter below I only compare the name field in equals:
xhtml:
<rich:pickList id="pickListUserGroupSelection"
value="#{usersBean.selectedUserGroups}" switchByDblClick="true"
sourceCaption="Available user groups" targetCaption="Groups assigned to user"
listWidth="365px" listHeight="100px" orderable="false"
converter="#{userGroupConverter}"
disabled="#{!rich:isUserInRole('USERS_MAINTAIN')}">
<f:validateRequired disabled="true" />
<rich:validator disabled="true" />
<f:selectItems value="#{usersBean.userGroups}" var="userGroup"
itemValue="#{userGroup}"
itemLabel="#{userGroup.name}" />
<f:selectItems value="#{usersBean.selectedUserGroups}" var="userGroup"
itemValue="#{userGroup}"
itemLabel="#{userGroup.name}" />
</rich:pickList>
<rich:message for="pickListUserGroupSelection" />
Converter:
package ...;
import ...
/**
* JSF UserGroup converter.<br>
* Description:<br>
* JSF UserGroup converter for rich:pickList elements.<br>
* <br>
* Copyright: Copyright (c) 2014<br>
*/
#Named
#Slf4j
#RequestScoped // must be request scoped, as it can change every time!
public class UserGroupConverter implements Converter, Serializable {
/**
*
*/
private static final long serialVersionUID = 9057357226886146751L;
#Getter
Map<String, UserGroup> groupMap;
#Inject
MessageUtil messageUtil;
#Inject
UserGroupDao userGroupDao;
#PostConstruct
public void postConstruct() {
groupMap = new HashMap<>();
List<UserGroup> userGroups;
try {
userGroups = userGroupDao.findAll(new String[] {UserGroup.FIELD_USER_ROLE_NAMES});
if(userGroups != null) {
for (UserGroup userGroup : userGroups) {
// 20150713: for some reason the UserGroup entity's equals method is not sufficient here and causes a JSF validation error
// "Validation Error: Value is not valid". I tried this overridden equals method and now it works fine :-)
#SuppressWarnings("serial")
UserGroup newGroup = new UserGroup() {
#Override
public boolean equals(Object obj){
if (!(obj instanceof UserGroup)){
return false;
}
return (getName() != null)
? getName().equals(((UserGroup) obj).getName())
: (obj == this);
}
};
newGroup.setName(userGroup.getName());
groupMap.put(newGroup.getName(), newGroup);
}
}
} catch (DaoException e) {
log.error(e.getMessage(), e);
FacesContext fc = FacesContext.getCurrentInstance();
FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_INFO, "Error initializing user group converter!", null);
fc.addMessage(null, message);
}
}
/*
* (non-Javadoc)
* #see javax.faces.convert.Converter#getAsObject(javax.faces.context.FacesContext, javax.faces.component.UIComponent, java.lang.String)
*/
#Override
public UserGroup getAsObject(FacesContext context, UIComponent component,
String value) {
UserGroup ug = null;
try {
ug = getGroupMap().get(value);
} catch (Exception e) {
log.error(e.getMessage(), e);
FacesContext fc = FacesContext.getCurrentInstance();
FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_INFO, "Error converting user group!", null);
fc.addMessage(null, message);
}
return ug;
}
/*
* (non-Javadoc)
* #see javax.faces.convert.Converter#getAsString(javax.faces.context.FacesContext, javax.faces.component.UIComponent, java.lang.Object)
*/
#Override
public String getAsString(FacesContext context, UIComponent component,
Object value) {
String name = ((UserGroup) value).getName();
return name;
}
}
Brgds and have fun!
Thanks Brian and BalusC for your help. I fixed my problem by adding named converter inside selectManyListbox & rich:picklist so they run well. But normally, I only use #FacesConverter(forClass = Staff.class), and don't need to add named converter in jsf so they still run well except for selectManyListbox & rich:picklist

How create a custom converter in JSF 2?

I have this Entity called 'Operation':
#Entity
#Table(name="operation")
public class Operation implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy=GenerationType.SEQUENCE)
private Integer id;
#NotNull(message="informe um tipo de operação")
private String operation;
//bi-directional many-to-one association to Product
#OneToMany(mappedBy="operation")
private List<Product> products;
// getter and setters
}
I retrieve the operations this way: (It could be through an EJB instance but just to keep it local and as an example, okay? ;) )
public Map<String, Object> getOperations() {
operations = new LinkedHashMap<String, Object>();
operations.put("Select an operation", new Operation());
operations.put("Donation", new Operation(new Integer(1), "donation"));
operations.put("Exchange", new Operation(new Integer(2), "exchange"));
return operations;
}
So I'm trying to get the selected operation in this selectOneMenu:
The productc is a ManagedBean which has a viewScope, productb is a ManagedBean with a sessionScope which has a product which is my entity. The product contais one operation, so is something like this:
(the letter c has the meaning of control, where all operations related about my entity product should be handled by this bean, okay?)
Product productc (ViewScope)
-- ProductBean productb (SessionScope)
---- Product product (Entity)
-------- Operation operation (Entity)
The converter is the same as #BalusC is suggest before:
#ManagedBean
#RequestScoped
public class OperationConverter implements Converter {
#EJB
private EaoOperation operationService;
#Override
public String getAsString(FacesContext context, UIComponent component, Object value) {
if (!(value instanceof Operation) || ((Operation) value).getId() == null) {
return null;
}
return String.valueOf(((Operation) value).getId());
}
#Override
public Object getAsObject(FacesContext context, UIComponent component, String value) {
if (value == null || !value.matches("\\d+")) {
return null;
}
Operation operation = operationService.find(Integer.valueOf(value));
System.out.println("Getting the operation value = " + operation.getOperation() );
if (operation == null) {
throw new ConverterException(new FacesMessage("Unknown operation ID: " + value));
}
return operation;
}
Which retrieve the operation selected as showed in the log:
FINE: SELECT ID, OPERATION FROM operation WHERE (ID = ?)
bind => [1 parameter bound]
INFO: Getting the operation value = exchange
So when I try to submit the form gives the follow error:
form_add_product:operation: Validation error: the value is not valid
Why is this happening?
You're trying to pass a complex object around as HTTP request parameter which can be only be a String. JSF/EL has builtin converters for primitives and its wrappers (e.g. int, Integer) and even enums. But for all other types you really need to write a custom converter. In this case you need to write a converter which converts between String and Operation. The String is then used as option value (open page in browser, rightclick and View Source and notice the <option value>). The Operation is then used as model value. The String should uniquely identify the Operation object. You could use operation ID for this.
But in this particular case, with such a hardcoded map and a relatively simple model, I think it's easier to use an enum instead.
public enum Operation {
DONATION("Donation"), EXCHANGE("Exchange");
private String label;
private Operation(String label) {
this.label = label;
}
public string getLabel() {
return label;
}
}
with
private Operation operation; // +getter +setter
public Operation[] getOperations() {
return Operation.values();
}
and
<h:selectOneMenu value="#{bean.operation}">
<f:selectItems value="#{bean.operations}" var="operation" itemValue="#{operation}" itemLabel="#{operation.label}" />
</h:selectOneMenu>
But if those values have actually to be retrieved from the DB and its size is undefinied, then you still really need a custom converter. You could in getAsString() return the ID and in getAsObject() use the operation DAO/EJB to get an Operation by the ID.
#ManagedBean
#RequestScoped
public class OperationConverter implements Converter {
#EJB
private OperationService operationService;
#Override
public String getAsString(FacesContext context, UIComponent component, Object value) {
// Convert here Operation object to String value for use in HTML.
if (!(value instanceof Operation) || ((Operation) value).getId() == null) {
return null;
}
return String.valueOf(((Operation) value).getId());
}
#Override
public Object getAsObject(FacesContext context, UIComponent component, String value) {
// Convert here String submitted value to Operation object.
if (value == null || !value.matches("\\d+")) {
return null;
}
Operation operation = operationService.find(Long.valueOf(value));
if (operation == null) {
throw new ConverterException(new FacesMessage("Unknown operation ID: " + value));
}
return operation;
}
}
Use it as follows:
<h:selectOneMenu ... converter="#{operationConverter}">
As to why it's a #ManagedBean instead of #FacesConverter, read this: Converting and validating GET request parameters.
Update as to the Validation Error: value not valid error, this means that the equals() method of the Operation class is broken or missing. During validation, JSF compares the submitted value with the list of available values by Object#equals(). If no one of the list matches with the submitted value, then you'll see this error. So, ensure that equals() is properly implemented. Here's a basic example which compares by the DB technical identity.
public boolean equals(Object other) {
return (other instanceof Operation) && (id != null)
? id.equals(((Operation) other).id)
: (other == this);
}
Don't forget to implement hashCode() as well:
public int hashCode() {
return (id != null)
? (getClass().hashCode() + id.hashCode())
: super.hashCode();
}

Resources