How to bind the selected value in selectOneMenu - jsf

Of five options in a selectOneMenu I chose the second option and persisted the entity.
On edit the persisted entity selectOneMenu always has the last option as its value.
For example,
<h:selectOneMenu value="#{userHome.user.leader}">
<f:selectItems value="#{userHome.availableLeaders}" var="leader" itemLabel="# {leader.name}" itemValue="#{leader}"/>
</h:selectOneMenu>
where availableLeaders is a list of users populated #PostConstruct method.
I am expecting the selectOneMenu to have the second option(chosen) on edit.
#FacesConverter(forClass = User.class, value = "userConverter")
public class UserConverter implements Converter {
public UserConverter() {
}
#Override
public Object getAsObject(FacesContext context, UIComponent component,
String value) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("DefaultPersistenceUnit");
EntityManager em = emf.createEntityManager();
Query q = em.createQuery("select query");
return q.resultList().get(0);
}
#Override
public String getAsString(FacesContext context, UIComponent component,
Object value) {
return ((User) value).getName();
}}
In User.java
public boolean equals(Object other) {
if (this.getClass().isInstance(other)) {
return true;
} else {
return false;
}
}
public int hashCode() {
HashCodeBuilder builder = new HashCodeBuilder();
builder.append(getId());
builder.append(getName());
return builder.toHashCode();
}

Look here:
public boolean equals(Object other) {
if (this.getClass().isInstance(other)) {
return true;
} else {
return false;
}
}
Your equals() method is definitely broken. This returns true for every other User object, even though it internally holds a completely different user ID/name. So the selected item matches every available select item value. That's why you see the last item being preselected everytime.
Assuming that the id property is unique for every user, then the equals() method should at its simplest rather look like this:
public boolean equals(Object other) {
if (!(other instanceof User)) {
return false;
}
if (other == this) {
return true;
}
if (id != null) {
return id.equals(((User) other).id);
}
return false;
}
which can also be summarized as follows
public boolean equals(Object other) {
return (other instanceof User) && (id != null) ? id.equals(((User) other).id) : (other == this);
}
Hint: a bit decent IDE like Eclipse can autogenerate equals() (and hashCode()) method.
See also:
Right way to implement equals contract
Generic reflective helper method for equals and hashCode

Related

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.

Converter in p:selectOneMenu

I have problem with my converter - it stops working and nothing happened.
I have Entity class "Group":
#Entity
#Table(name="users_group")
public class Group implements Serializable {
private int id;
private String name;
private boolean active = true;
private String code;
private List<User> users = new ArrayList<User>();
// getters, setters
#Override
public String toString() {
return "Group.id="+getId();
}
#Override
public boolean equals(Object obj){
System.out.println("OBJ :"+obj);
if(obj == null )return false;
if(obj instanceof String){
if(obj.toString().equals(this.toString())){
return true;
}
}
if(obj instanceof Group){
Group objGroup = (Group) obj;
if(objGroup.getId() == this.getId()){
System.out.println("EUREKA! Found");
return true;
}
}
return false;
}
}
here is my jsf view code:
<p:selectOneMenu id="group" value="#{priviligeMB.groupPrivilige.group}" converter="#{groupConverter}" >
<f:selectItems value="#{priviligeMB.groups}"
var="group" itemLabel="#{group.name}" itemValue="#{group}" />
</p:selectOneMenu >
And of course GroupConverter class:
#Component("groupConverter")
public class GroupConverter implements Converter {
#Autowired
GroupService groupService;
#Override
public Object getAsObject(FacesContext arg0, UIComponent arg1, String arg2) {
// arg2 is "Group.id=x" where x is just an id, for example "Group.id=2"
try {
System.out.println("getasObj: "+arg2);
return groupService.getGroupById(Integer.parseInt(arg2.split("=")[1]));
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
#Override
public String getAsString(FacesContext arg0, UIComponent arg1, Object arg2) {
// arg2 is object with toString() method that prints: "Group.id=x"
System.out.println("getAsString: "+arg2);
return arg2.toString();
}
}
Here is my log:
> getAsString: Group.id=1
> getAsString: Group.id=2
> Hibernate: SELECT locale.code from messages_locale locale left join users_user u sers on
> users.locale_id = locale.id where users.username =?
> getasObj: Group.id=1
> Hibernate: select group0_.id as id0_0_, group0_.active as
> active0_0_, group0_.co de as code0_0_, group0_.name as name0_0_ from
> users_group group0_ where group0_. id=?
> OBJ :Group.id=1 EUREKA! Found
> OBJ :Group.id=1 EUREKA! Found
> OBJ :Group.id=2
And after that... nothing happened! No errors, no actions, no page reload, no messages...
I got the similar problem and finally it's solved.
In such case, you need to make sure the object returned by "getAsObject" can match one of the objects in "#{priviligeMB.groups}", here "match" means the same object.

Avoid extra DB reads in the getAsObject method of converter class by caching data client side?

I'm showing a list of suggested items in an autocomplete input element. For that I need to implement a converter to convert the entity<entityName, entityId> to entityName & vice versa. However while implementing that I realized that I had to read the DB more than 1 time to find the corresponding entityId for the chosen entityName(while getAsObject()), I am wondering why isn't this stored somewhere client side so that the entityId could be passed when the entityname is selected.
Is there any way I could avoid this extra read?
This is indeed "by design" and perhaps a little oversight in the JSF spec. You can in theory perfectly avoid it by extracting the items from the UIComponent argument and comparing against them instead. It's however a bit of work. My colleague Arjan Tijms has written a blog about this: Automatic to-Object conversion in JSF selectOneMenu & Co.
Here's an extract of relevance; the below is the base converter which you'd need to extend instead:
public abstract class SelectItemsBaseConverter implements Converter {
#Override
public Object getAsObject(FacesContext context, UIComponent component, String value) {
return SelectItemsUtils.findValueByStringConversion(context, component, value, this);
}
}
Here's the SelectItemsUtils class which is partly copied from Mojarra's source:
public final class SelectItemsUtils {
private SelectItemsUtils() {}
public static Object findValueByStringConversion(FacesContext context, UIComponent component, String value, Converter converter) {
return findValueByStringConversion(context, component, new SelectItemsIterator(context, component), value, converter);
}
private static Object findValueByStringConversion(FacesContext context, UIComponent component, Iterator<SelectItem> items, String value, Converter converter) {
while (items.hasNext()) {
SelectItem item = items.next();
if (item instanceof SelectItemGroup) {
SelectItem subitems[] = ((SelectItemGroup) item).getSelectItems();
if (!isEmpty(subitems)) {
Object object = findValueByStringConversion(context, component, new ArrayIterator(subitems), value, converter);
if (object != null) {
return object;
}
}
} else if (!item.isNoSelectionOption() && value.equals(converter.getAsString(context, component, item.getValue()))) {
return item.getValue();
}
}
return null;
}
public static boolean isEmpty(Object[] array) {
return array == null || array.length == 0;
}
/**
* This class is based on Mojarra version
*/
static class ArrayIterator implements Iterator<SelectItem> {
public ArrayIterator(SelectItem items[]) {
this.items = items;
}
private SelectItem items[];
private int index = 0;
public boolean hasNext() {
return (index < items.length);
}
public SelectItem next() {
try {
return (items[index++]);
}
catch (IndexOutOfBoundsException e) {
throw new NoSuchElementException();
}
}
public void remove() {
throw new UnsupportedOperationException();
}
}
}
Here's how you should use it for your own converter, you only have to implement getAsString() (the getAsObject() is already handled):
#FacesConverter("someEntitySelectItemsConverter")
public class SomeEntitySelectItemsConverter extends SelectItemsBaseConverter {
#Override
public String getAsString(FacesContext context, UIComponent component, Object value) {
return ((SomeEntity) value).getId().toString();
}
}
Update the above concept has ended up in JSF utility library OmniFaces in flavor of the following converters:
SelectItemsConverter - for <f:selectItem(s)> based on Object#toString().
SelectItemsIndexConverter - for <f:selectItem(s)> based on item's index.
ListConverter - for e.g. <p:autoComplete> based on Object#toString()
ListIndexConverter - for e.g. <p:autoComplete> based on item's index.
The only way I have found to do this so that your converter does not need to access the DB, is to make the Converter a managed bean so that it can access some other bean which stores the list of suggested values of the AutoComplete component.
Something like this:
#ManagedBean
#RequestScoped
public class EntityConverter implements Converter
{
#ManagedProperty(value = "#{autoCompleteBean}")
private AutoCompleteBean autoCompleteBean;
public void setAutoCompleteBean(AutoCompleteBean autoCompleteBean)
{
this.autoCompleteBean = autoCompleteBean;
}
#Override
public Object getAsObject(FacesContext context, UIComponent component,
String value)
{
final List<Entity> entities = autoCompleteBean.getCachedSuggestions();
for (final Enity entity : entities)
{
if (entity.getIdAsString().equals(value))
{
return entity;
}
}
throw new IllegalStateException("Entity was not found!");
}
#Override
public String getAsString(FacesContext context, UIComponent component,
Object value)
{ ... }
In your jsf page, make sure you reference the converter as a bean. ie:
<p:autoComplete value="#{autoCompleteBean.selectedEntity}"
completeMethod="#{autoCompleteBean.getSuggestions}" var="theEntity"
itemValue="#{theEntity}" itemLabel=#{theEntity.someValue}
converter="#{entityConverter}">

selectOneMenu do not render/display value

Made a post earlier about this topic (Display values in drill-down SelectOneMenus). The problem that have is not connected to the Object converters as I first thought.
I have two selectOneMenu depending on each other, Sector -> Category. When I persist the background business object that holds a Sector and Category, everything works as expected. Even after the current user session terminates and the user log on again and edit the previous saved business object.
The problem occur when and the entity manager flushes the objects and user wants to display the Sector, Category again from the database. The Sector selectOneMenu displays its value as it should, but the Category selectOneMenu value is not displayed. Although the backing bean has the correct value from the database.
What makes the selectOneMenuto not display certain persisted values/objects when they are loaded from database instead from in-memory?
Edit.xhtml
<h:outputLabel value="Sector:" />
<h:selectOneMenu id="sectorSelector" value="#{activityController.selected.category.sector}" title="#{bundle.CreateSectorLabel_sectorName}" valueChangeListener="#{activityController.changeSectorMenu}" immediate="true">
<a4j:ajax event="change" execute="#this categoryMenu" render="categoryMenu"/>
<f:selectItems value="#{sectorController.itemsAvailableSelectOne}"/>
</h:selectOneMenu>
<h:outputLabel value="Category:" />
<h:selectOneMenu id="categoryMenu" value="#{activityController.selected.category}" title="#{bundle.CreateSectorLabel_sectorName}"
binding="#{activityController.categoryMenu}"
required="true" requiredMessage="#{bundle.CreateCategoryRequiredMessage_sector}">
<f:selectItems value="#{activityController.categorySelection}"/>
</h:selectOneMenu>
Controller bean for Category
#ManagedBean(name = "categoryController")
#SessionScoped
public class CategoryController implements Serializable{
....
#FacesConverter(forClass = Category.class)
public static class CategoryControllerConverter implements Converter {
#Override
public Object getAsObject(FacesContext facesContext, UIComponent component, String value) {
if (value == null || value.length() == 0) {
return null;
}
CategoryController controller = (CategoryController) facesContext.getApplication().getELResolver().
getValue(facesContext.getELContext(), null, "categoryController");
return controller.ejbFacade.find(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();
}
#Override
public String getAsString(FacesContext facesContext, UIComponent component, Object object) {
if (object == null) {
return null;
}
if (object instanceof Category) {
Category o = (Category) object;
return getStringKey(o.getIdCategory());
}
else {
throw new IllegalArgumentException("object " + object + " is of type " + object.getClass().getName() + "; expected type: " + CategoryController.class.getName());
}
}
}
Part of POJO object
...
public class Category implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#Column(name = "idCategory")
private Integer idCategory;
...
#Override
public boolean equals(Object object) {
if (!(object instanceof Category)) {
return false;
}
Category other = (Category) object;
if ((this.idCategory == null && other.idCategory != null) || (this.idCategory != null && !this.idCategory.equals(other.idCategory))) {
return false;
}
return true;
}
Functon for creating the SelectItem[] array
public static SelectItem[] getSelectItems(List<?> entities, boolean selectOne) {
int size = entities.size();
SelectItem[] items;
int i = 0;
if (selectOne) {
items = new SelectItem[size + 1];
items[0] = new SelectItem("", ResourceBundle.getBundle("/resources/Bundle").getString("Select_item"));
i++;
} else {
items = new SelectItem[size];
}
for (Object x : entities) {
items[i++] = new SelectItem(x, x.toString());
}
return items;
}
The problem was that I didn't have the right collection of SelectItems when the user edited a business object. Plus that I added the <f:converter> to explicitly invoke the converter, where I did some changes in the getAsString() (the else part).
Thank you for your time, and hope the post can be useful to someone else.
SelectMenuOne
public String prepareEditOrganisationActivity() {
current = (Activity) organisationActivityItems.getRowData();
Sector sector = current.getCategory().getSector();
CategoryController categoryBean = JsfUtil.findBean("categoryController", CategoryController.class);
categorySelection = categoryBean.categoryItemsBySector(sector);
return "EditActivity";
}
CategoryControllerConverter
...
#Override
public String getAsString(FacesContext facesContext, UIComponent component, Object object) {
if (object == null) {
return null;
}
if (object instanceof Category) {
Category o = (Category) object;
return getStringKey(o.getIdCategory());
}
else {
Category tmp = new Category();
return getStringKey(tmp.getIdCategory());
}
}

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