@FacesConverter(forClass = Clazz.class) and p:cale

2019-07-14 19:58发布

问题:

Basic Joda Time converter (the code is absolutely superfluous for the context of this thread) :

@Named
@ApplicationScoped
@FacesConverter(forClass = DateTime.class)
public class DateTimeConverter implements Converter {

    @Override
    public Object getAsObject(FacesContext context, UIComponent component, String value) {
        if (value == null || value.isEmpty()) {
            return null;
        }

        try {
            return DateTimeFormat.forPattern("dd-MMM-yyyy hh:mm:ss aa Z").parseDateTime(value).withZone(DateTimeZone.UTC);
        } catch (IllegalArgumentException | UnsupportedOperationException e) {
            throw new ConverterException(new FacesMessage(FacesMessage.SEVERITY_ERROR, null, "Message"), e);
        }
    }

    @Override
    public String getAsString(FacesContext context, UIComponent component, Object value) {
        if (value == null) {
            return "";
        }

        if (!(value instanceof DateTime)) {
            throw new ConverterException("Error");
        }

        try {
            return DateTimeFormat.forPattern("dd-MMM-yyyy hh:mm:ss aa Z").print(((DateTime) value).withZone(DateTimeZone.forID("zoneId")));
        } catch (IllegalArgumentException e) {
            throw new ConverterException("Error", e); // Not required.
        }
    }
}

Why does it not work with a <p:calendar> unless/until it is explicitly specified by using the converter attribute?

<p:calendar converter="#{dateTimeConverter}" value="{bean.dateTimeValue}" .../>

Like other components, it is expected to work without mentioning the converter attribute because the converter is decorated with

@FacesConverter(forClass = DateTime.class)

Is this feature not supported by <p:calendar>?

Using PrimeFaces 5.2 and JSF 2.2.12.

回答1:

Based on 5.2's CalendarRenderer#encodeEnd(), it uses CalendarUtils#getValueAsString() to obtain the value for output. It indeed doesn't consult via Application#createConverter(Class) if there's a converter by class.

51          //first ask the converter
52          if(calendar.getConverter() != null) {
53              return calendar.getConverter().getAsString(context, calendar, value);
54          }
55          //Use built-in converter
56          else {
57              SimpleDateFormat dateFormat = new SimpleDateFormat(calendar.calculatePattern(), calendar.calculateLocale(context));
58              dateFormat.setTimeZone(calendar.calculateTimeZone());
59              
60              return dateFormat.format(value);
61          }

That confirms the behavior you observed. You'd best create an issue report to PrimeFaces guys which requests adding an instanceof Date check in place, and in the opposite case obtain the converter by class from the application something like below:

Converter converter = context.getApplication().createConverter(value.getClass());
if (converter != null) {
    return converter.getAsString(context, calendar, value);
}
else {
    throw new IllegalArgumentException(value.getClass());
}                    

This would indeed make sense given the increasing use of Java8's java.time API. There are by the way a few other places in CalendarRenderer and CalendarUtils where this could/should be implemented (and where they are actually performing an instanceof check, but not delegating it to the converter by class, if any).