Help

Built with Seam

You can find the full source code for this website in the Seam package in the directory /examples/wiki. It is licensed under the LGPL.

Drop-down (or combo) boxes are problematic in JSF. First, consider how these form elements work in HTML:

A drop-down box in a form is using two kinds of values. The first value is the label of each item in the list, which is what your users see and what they click on. The second value is the internal value of the list item, which is what will be send to the server after the user clicks. All of these values are strings.

Therefore, it is quite straightforward to create a JSF combo-box that displays a List<String>, and that invokes a selectString(s) action when an item is selected.

What if you have a List<Customer> and you want the selectCustomer(c) method to be called?

As the HTML and form submit will only support string values, you have to convert Customer entity instances to and from strings. That is the job of the built-in Seam entity converter, available as <s:convertEntity/> in your form template.

The JSF specification requirement and the broken implementation

But there is another catch, which is the JSF reference implementation. Let's first look at the JSF 1.0 specification (sections 4.1.15.3 and 4.1.16.3):

...must provide a specialized validate() method which ensures that any decoded value is a valid option (from the nested UISelectItem and UISelectItems children).

The goal of this is clear: When a string value is submitted by the user with the form, the JSF backend has to do some extra validation to ensure that the value is actually one of the possible values. In other words, if you present your user a list of strings in a drop-down box, you want to make sure that what is submitted is really one of those strings, not something else that might be potentially dangerous. If it is not valid, JSF will queue a message with the text Value is not valid, which is very very confusing for new JSF users.

The issue is the JSF reference implementation and how it implements this requirement. The mentioned specialized validate() method is in there and it does something which is OK for a list of strings: It takes the submitted value and compares it with every item in the list: submittedValue.equals(eachAndEveryListValue). This is great for strings, because they are equal if they have the same characters.

How does the Seam entity converter handle this behavior, when it converts a Customer from and to a string?

When your drop down box is rendered, the List<Customer> is transformed into a list of strings, each string is the entity identifier value of the Customer instance.

When the drop down box is submitted, the submitted string value is converted back into a Customer instance by loading it from the database, using the identifier primary key.

Now the problem: The Customer instance that was just loaded from the database is compared with the List<Customer> elements using a.equals(b). It has to match one of the list items, or it won't be a valid selection.

THIS ONLY WORKS WHEN List<CUSTOMER> AND THE Customer INSTANCE ARE USING THE SAME PERSISTENCE CONTEXT! OR WHEN Customer OVERRIDES THE equals() METHOD ACCORDINGLY!

When the form is submitted, the List<Customer> has to be the same or return the same instances (as in a == b, the default equals() in java.lang.Object) that have been used to render the list 5 minutes earlier. Typically, you'd use a Seam long-running conversation and a conversation-scoped component as the backing bean for your JSF form, so that the EntityManager and the persistence context are held across requests and you will get the same entity instances all the time.

This can be very inconvenient if you just want a drop-down box of List<Customer> and no long-running conversation.

A convenient workaround

The following custom entity converter is a replacement for <s:convertEntity/> and it tries to work around the JSF reference implementation behavior. When a selection is submitted, it is converted back to an instance of Customer (or whatever entity you have), but that instance is then discarded and an equal instance of the original customer list is returned. Getting that original list is a bit tricky, but possible:

import org.jboss.seam.ScopeType;
import org.jboss.seam.annotations.Install;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.annotations.Scope;
import org.jboss.seam.annotations.Transactional;
import org.jboss.seam.annotations.faces.Converter;
import org.jboss.seam.annotations.intercept.BypassInterceptors;
import org.jboss.seam.ui.AbstractEntityLoader;

import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.convert.ConverterException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.io.Serializable;
import java.util.Collection;
import java.util.Set;
import java.util.HashSet;

/**
 * Enhances the Seam entity converter and avoids "Value is not valid" validation errors by
 * not only decoding a requested entity identifier and loading that entity, but also returning
 * the matching (configurable) original instance from the select list. This solves the following
 * problem if you are not using a long-running CONVERSATION:
 * <p/>
 * <a href="http://in.relation.to/2708.lace">Dropdowns in JSF: Validating the selected value</a>
 *
 * @author Christian Bauer
 */
@Name("matchingEntityConverter")
@Scope(ScopeType.CONVERSATION)
@Install(precedence = Install.APPLICATION)
@Converter
@BypassInterceptors
public class MatchingEntityConverter implements javax.faces.convert.Converter, Serializable {

    private AbstractEntityLoader entityLoader;

    public AbstractEntityLoader getEntityLoader() {
        if (entityLoader == null) {
            return AbstractEntityLoader.instance();
        } else {
            return entityLoader;
        }
    }

    public void setEntityLoader(AbstractEntityLoader entityLoader) {
        this.entityLoader = entityLoader;
    }

    @SuppressWarnings("unchecked")
    @Transactional
    public String getAsString(FacesContext facesContext, UIComponent cmp, Object value) throws ConverterException {
        if (value == null) {
            return null;
        }
        if (value instanceof String) {
            return (String) value;
        }
        return getEntityLoader().put(value);
    }


    @Transactional
    public Object getAsObject(FacesContext facesContext, UIComponent cmp, String value) throws ConverterException {
        if (value == null) {
            return null;
        }
        Object loadedInstance = getEntityLoader().get(value);

        // THIS IS THE EXTENSION THAT MAKES THIS SPECIAL:

        // We will try to return the _original_value from the _original_ list, instead of the instance that
        // we just loaded from the database. This makes validation work, because equals() will be called on the
        // returned instance and it will be compared to all items in the list. If the instance we just loaded
        // is therefore not equal() to an item of the list, we'd get a "value is not valid" error. Returning one
        // of the instances from the list avoids that. We implement our own matching routine here, configurable.

        Object equalListInstance = null;
        try {

            Object loadedPropertyValue = null;
            if (loadedInstance != null) {
                loadedPropertyValue = readMatchingProperty(loadedInstance);
            }

            if (loadedPropertyValue != null) {

                Set originalList = getOriginalSelectItems(cmp);
                for (Object listInstance : originalList) {

                    Object listItemPropertyValue = readMatchingProperty(listInstance);
                    if (listItemPropertyValue != null && loadedPropertyValue.equals(listItemPropertyValue)) {
                        equalListInstance = listInstance;
                        break;
                    }
                }
            }
        } catch (Exception ex) {
            throw new ConverterException(ex);
        }

        // Fall back to loadedInstance.equals(<for each listInstance>) which is what the JSF RI implementors
        // do in their JPA-unfriendly validation routine that will run after this...
        return equalListInstance != null ? equalListInstance : loadedInstance;
    }

    protected Object readMatchingProperty(Object o) throws Exception {
        PropertyDescriptor[] props = Introspector.getBeanInfo(o.getClass()).getPropertyDescriptors();
        for (PropertyDescriptor descriptor : props) {
            if (descriptor.getName().equals(getMatch())) {
                return descriptor.getReadMethod().invoke(o);
            }
        }
        return null;
    }

    protected Set getOriginalSelectItems(UIComponent cmp) {
        Set items = new HashSet();
        // This is really ugly, but what can you do...
        for (UIComponent child : cmp.getChildren()) {
            if (child instanceof javax.faces.component.UISelectItems) {
                javax.faces.component.UISelectItems selectItems = (javax.faces.component.UISelectItems) child;
                for (Object selectItemsValue : (Collection) selectItems.getValue()) {
                    javax.faces.model.SelectItem selectItem = (javax.faces.model.SelectItem) selectItemsValue;
                    if (selectItem.getValue() == null) continue;
                    items.add(selectItem.getValue());
                }
                break;
            }
        }
        return items;
    }

    private String match;

    public String getMatch() {
        if (match == null) {
            return "id"; // Most entities would have that property
        }
        return match;
    }

    public void setMatch(String match) {
        this.match = match;
    }
}

How Customer, Product, or whatever instances are compared and considered equal is configurable, by default a property called id is expected, but you can override this in your components.xml:

<component name="identifierMatchingEntityConverter"
           class="common.web.MatchingEntityConverter">
    <property name="match">id</property> <!-- That's actually the default property we'd use for comparison-->
</component>

Use it like this in your templates - note the AJAX automatic submit, this is why you'd want the PAGE scope instead of a long-running conversation:

<h:selectOneMenu value="#{browseProducts.selectedProduct}"
                 converter="#{identifierMatchingEntityConverter}"> <!-- The magic that makes it work! -->
    <s:selectItems value="#{browseProducts.list}"
                   var="p"
                   label="#{p.name}"
                   noSelectionLabel="NO PRODUCT SELECTED">
    </s:selectItems>
    <a:support event="onchange" action="#{browseProducts.productSelected}/>
</h:selectOneMenu>

The browseProduct backing bean is in PAGE scope. This new converter makes it work without a long-running conversation scope and with two different persistence contexts before and after the submit:

@Name("browseProducts")
@Scope(ScopeType.PAGE)
public class BrowseProducts {

    public List<Product> getList() {
        ... // Load the Products from the database
    }

    private Product selectedProduct;

    public Product getSelectedProduct() {
        return selectedProduct;
    }

    public void setSelectedProduct(Product selectedProduct) {
        this.selectedProduct = selectedProduct;
    }

    public void productSelected() { ... // Do stuff }
}