6
votes

In a "create new user" jsf page, I have a SelectOneMenu with a custom converter and a noSelectionOption selectItem like this: (irrelevant code ommited)

NewUser.xhtml

<h:form>
<h:selectOneMenu value="#{newUserController.user.department}" 
                 required="true" converter="departmentConverter">
    <f:selectItem itemLabel="Select a department" noSelectionOption="true"/>
    <f:selectItems value="#{newUserController.departments}"
                   var="dep" itemLabel="#{dep.name}" itemValue="#{dep}"/>
</h:selectOneMenu>
<p:commandButton action="#{newUserController.saveUser}"
                 value="#{bundle.Save}"
                 ajax="false"/>
</h:form>

NewUserController.java

@ManagedBean
@ViewScoped
public class NewUserController implements Serializable {
private static final long serialVersionUID = 10L;

@EJB private UserBean userBean;
private List<Department> departments;
private User user;

public NewUserController () {
}

@PostConstruct
public void init(){
    user = new User();
    departments = userBean.findAllDepartments();
}

public User getUser() {
    return user;
}

public void setUser(User user) {
    this.user = user;
}

public List<Department> getDepartments(){
    return departments;
}

public String saveUser() {
    // Business logic
}
}

DepartmentConverter.java

@FacesConverter(value="departmentConverter")
public class DepartmentConverter extends EntityConverter {
    public DepartmentConverter(){
        super(Department.class);
    }
}

Super converter for all entities

public class EntityConverter<E> implements Converter{
protected Class<E> entityClass;

public EntityConverter(Class<E> type) {
    entityClass = type;
}

@Override
public Object getAsObject(FacesContext facesContext, UIComponent component, String value) {
    if (value == null || value.length() == 0) {
        return null;
    }
    try {
        InitialContext ic = new InitialContext();
        UserBean ub = (UserBean)ic.lookup("java:global/CompetenceRegister/UserBean");
        return ub.find(entityClass, getKey(value));
    } catch (NamingException e) {
        return null;
    }
}

Long getKey(String value) {
    Long key;
    key = Long.valueOf(value);
    return key;
}

String getStringKey(Long value) {
    StringBuilder sb = new StringBuilder();
    sb.append(value);
    return sb.toString();
}

@Override
public String getAsString(FacesContext facesContext, UIComponent component, Object object) {
    if (object == null) {
        return null;
    }
    if (object instanceof AbstractEntity) {
        AbstractEntity e = (AbstractEntity) object;
        return getStringKey(e.getId());
    }
    else
        throw new IllegalArgumentException("object " + object + " is of type " + object.getClass().getName() + "; expected type: " + entityClass.getName());
}

}

However, when i post the form with the "Select a department" option chosen, it sends the label to getAsObject in the converter instead of null, thus causing the converter to throw an exception in getKey (tries to convert a String containing an id to a Long). Setting the itemValue attribute of the selectItem to null has no effect. The items from the collection works perfectly otherwise with the converter. Does anyone have any idea of what is causing this?

Update An interesting thing I forgot to mention; if I remove the converter attribute from the SelectOneMenu, the noSelectionAttribute works as it should, but since the default converter doesn't know how to convert my objects, the post fails on selecting a true department instead. Can this mean that the noSelectionOption=true is SUPPOSED to send its label instead and the converter is somehow expected to handle it?

2
Using Jsf 2. Sorry for not clarifying.Rasmus Franke
@Shervin: f:selectItem noSelectionOption and f:selectItems var indicates JSF2.BalusC
Your facelet markup seems correct. How about including the code for your departmentConverter, and for your newUserController.Brian Leathem
I did not include the converter since it felt irrelevant because the problem being how it is called rather than its own functionality, and the controller is very straight forward. I have however updated the post so you can see pretty much everything now.Rasmus Franke

2 Answers

7
votes

My problem was switching to using the converter attribute of SelectOneMenu instead of using the forClass attribute of FacesConverter.

switching

@FacesConverter(value="departmentConverter")
public class DepartmentConverter extends EntityConverter {
public DepartmentConverter(){
    super(Department.class);
}
}

to

@FacesConverter(forClass=Department.class)
public class DepartmentConverter extends EntityConverter {
public DepartmentConverter(){
    super(Department.class);
}
}

causes my own converter to be used for real values while the default converter (null converter? I have not been able to find the source code for it) is used when the NoSelectionOption attribute is set to true. My theory is that setting this attribute to true sets the type of the value to null with the label as value causing it to go to a special converter always returning null or something similar. Using the converter attribute instead of forClass causes my own converter to always be used regardless of type, and I would thus have to handle the label being sent as value myself.

1
votes

One solution that works is to simply add

try{
    return ub.find(entityClass, getKey(value));
}catch(NumberFormatException e){ // Value isn't a long and thus not an id.
    return null;
}

to the getAsObject in EntityConverter, but this feels like I am fixing the problem in the wrong place. Sending the label as value simply does not make sense, it should really send NULL.