5

How do I write a converter for a list of items of class A in JSF2? I have written a converter for class A, but the items show up using the default toString() function: "A@hashcode".

I need to use a converter rather than a backing bean method so that validation can take place (Hibernate Validator).

more info

This is how I use the list:

<h:inputText id="destinations" value="#{rule.destinations}" converter="gr.panayk.vinyls.Destination"/>

Where #{rule.destinations} is of List<Destination> type. I am expecting a comma separated list of converted Destinations.

solution

I attach the List converter that BalusC proposed.

@FacesConverter(value="gr.panayk.vinyls.converter.DestinationList")
public class DestinationListConverter implements Converter
{
    @Override
    public Object getAsObject(final FacesContext context, final UIComponent component, final String values)
    {
        final Validator validator = Validation.buildDefaultValidatorFactory().getValidator();

        final List<Destination> result = new ArrayList<Destination>(); 
        for (String value : values.split(",", -1))
        {           
            final String trimmedValue = value.trim();

            final Set<ConstraintViolation<Destination>> violations = validator.validateValue(Destination.class, "data", trimmedValue);
            if (!violations.isEmpty())
            {
                throw new ConverterException(new FacesMessage(violations.iterator().next().getMessage()));
            }

            result.add(new Destination(trimmedValue));
        }       

        final Set<ConstraintViolation<Rule>> violations = validator.validateValue(Rule.class, "destinations", result);
        if (!violations.isEmpty())
        {
            throw new ConverterException(new FacesMessage(violations.iterator().next().getMessage()));
        }       

        return result;
    }

    @Override
    public String getAsString(final FacesContext context, final UIComponent component, final Object value)
    {
        if (value instanceof List<?>)
        {
            final StringBuffer result = new StringBuffer();

            final List<?> list = (List<?>) value;

            for (int i = 0; i < list.size()-1; i++)
            {               
                if (list.get(i) instanceof Destination)
                {
                    result.append(((Destination) list.get(i)).getData());
                    result.append(", ");
                }
                else
                {
                    throw new IllegalArgumentException( "Cannot convert " + value + " object to Destination in DestinationConverter." );
                }
            }

            if (!list.isEmpty())
            {
                if (list.get(list.size()-1) instanceof Destination)
                {
                    result.append(((Destination) list.get(list.size()-1)).getData());
                }
                else
                {
                    throw new IllegalArgumentException( "Cannot convert " + value + " object to Destination in DestinationConverter." );
                }
            }

            return result.toString();
        }
        else
        {
            throw new IllegalArgumentException( "Cannot convert " + value + " object to List in DestinationConverter." );
        }
    }
}
Panayiotis Karabassis
  • 2,278
  • 3
  • 25
  • 40

1 Answers1

6

I have written a converter for class A, but the items show up using the default toString() function: "A@hashcode".

That can happen if you didn't explicitly declare the converter on the component. In for example <h:selectManyCheckbox> and <h:selectManyListbox> explicitly declaring the converter is mandatory as all JSF/EL knows is that the value is of type List, not List<A> (generic types are lost during runtime). If you don't declare a converter, then the values will be treated as String (as that's what HTML output and HTTP request parameter values default to).

E.g.

<h:selectManyCheckbox converter="aConverter">

with

@FacesConverter(value="aConverter", forClass=A.class)
public class AConverter implements Converter {

    // ...

}

Explicitly declaring the above converter is not necessary when you're using single-item inputs like <h:selectOneMenu> as the forClass would match it anyway.

BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
  • Now I get: java.lang.IllegalArgumentException: Cannot convert [gr.panayk.vinyls.model.Destination@59c527be, gr.panayk.vinyls.model.Destination@679b622d] object in DestinationConverter. It's a message thrown by my converter. It seems it's trying to convert the *list* to an item. I'll add more info to my use case. – Panayiotis Karabassis Oct 02 '11 at 12:23
  • BTW, thanks. I think you are the top expert in JSF. Let me know if you want more information. – Panayiotis Karabassis Oct 02 '11 at 12:31
  • 1
    You just need to write code accordingly which does the `List` <--> `String` conversion job in `getAsString()` and `getAsObject()` (building a comma separated string and splitting on comma respectively). This is however an very specific use case which makes the converter unreuseable for normal inputs which take/return a single item multiple times. – BalusC Oct 02 '11 at 19:29
  • It's ok, I won't be using List anywhere else. Thanks! – Panayiotis Karabassis Oct 03 '11 at 03:18