4

We now started to use JSF 2.3 on our existing JSF 2.2 project. On our custom converters we got warning Converter is a raw type. References to generic type Converter<T> should be parameterized. Problem that we experiencing is when we tried to fix that warning using generics:

@FacesConverter(value = "myConverter", managed = true)
public class MyConverter implements Converter<MyCustomObject>{  

@Override
public MyCustomObject getAsObject(FacesContext context, UIComponent component, String submittedValue){}

@Override
public String getAsString(FacesContext context, UIComponent component, MyCustomObject modelValue) {}
}

and when converter is used for example in

<!DOCTYPE html>
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
xmlns:c="http://xmlns.jcp.org/jsp/jstl/core"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:ui="http://java.sun.com/jsf/facelets">
<h:selectOneMenu id="#{componentId}" value="#{componentValue}">
    <f:converter converterId="myConverter" />
    <f:selectItem itemLabel="label"
        itemValue="" />
    <f:selectItems value="listOfValues"
        var="singleValue"
        itemValue="singleValue.value"
        itemLabel="singleValue.label" />
</h:selectOneMenu>

then ClassCastException with message java.lang.String cannot be cast to MyCustomObjectis thrown. There is also one line in stacktrace that maybe can help com.sun.faces.cdi.CdiConverter.getAsString(CdiConverter.java:109).

But when converter generics definition changed from MyCustomObject to Object :

@FacesConverter(value = "myConverter", managed = true)    
public class MyConverter implements Converter<Object>{  

@Override
public Object getAsObject(FacesContext context, UIComponent component, String submittedValue){}

@Override
public String getAsString(FacesContext context, UIComponent component, Object modelValue) {}
}

then everything works as expected, but that obviously beats the purpose of Converter<T> interface.

Znas Me
  • 190
  • 1
  • 15

2 Answers2

2

I had same issue here and came up with following solution that actually not just compiles but also runs well:

some_page.xhtml (relevant excerpt):

<h:selectOneMenu styleClass="select" id="companyUserOwner" value="#{adminCompanyDataController.companyUserOwner}">
    <f:converter converterId="UserConverter" />
    <f:selectItem itemValue="#{null}" itemLabel="#{msg.NONE_SELECTED}" />
    <f:selectItems value="#{userController.allUsers()}" var="companyUserOwner" itemValue="#{companyUserOwner}" itemLabel="#{companyUserOwner.userContact.contactFirstName} #{companyUserOwner.userContact.contactFamilyName} (#{companyUserOwner.userName})" />
</h:selectOneMenu>

Please note that the above JSF code is full of custom stuff and may not work on your end. And that my converter compiles without rawtype warning (JSF 2.3+, not 2.2!):

SomeUserConverter.java:

@FacesConverter (value = "UserConverter")
public class SomeUserConverter implements Converter<User> {

    /**
     * User EJB
     */
    private static UserSessionBeanRemote USER_BEAN;

    /**
     * Default constructor
     */
    public SomeUserConverter () {
    }

    @Override
    public User getAsObject (final FacesContext context, final UIComponent component, final String submittedValue) {
        // Is the value null or empty?
        if ((null == submittedValue) || (submittedValue.trim().isEmpty())) {
            // Return null
            return null;
        }

        // Init instance
        User user = null;

        try {
            // Try to parse the value as long
            final Long userId = Long.valueOf(submittedValue);

            // Try to get user instance from it
            user = USER_BEAN.findUserById(userId);
        } catch (final NumberFormatException ex) {
            // Throw again
            throw new ConverterException(ex);
        } catch (final UserNotFoundException ex) {
            // User was not found, return null
        }

        // Return it
        return user;
    }

    @Override
    public String getAsString (final FacesContext context, final UIComponent component, final User value) {
        // Is the object null?
        if ((null == value) || (String.valueOf(value).isEmpty())) {
            // Is null
            return ""; //NOI18N
        }

        // Return id number
        return String.valueOf(value.getUserId());
    }

}

This converter class has the JNDI lookup removed (I will rewrite that part later anyway) but it should be enough for demonstrating the important parts:

  • Converter<User> (by User is a custom POJI) preventing raw-type warning
  • value="UserConverter" that is the actual name you use in your JSF page
  • And most important, which actually fixed the ClassCastException:
  • <f:selectItem itemValue="#{null}" itemLabel="#{msg.NONE_SELECTED}" />
  • By omitting #{null} an empty string instead of null is being handled over to your getAsString method!

This last one actually fixed the said exception and my application is working again with much lesser warnings and much better type-hinting.

Need to remove forClass as value is there: FacesConverter is using both value andforClass, only value will be applied.

The raw-type warning will come (since Java SE 5) because you use an interface which has a generic (mostly stated as <T> after the interface name) which you need to satisfy so the compiler stop throwing that warning at you.

Roland
  • 184
  • 1
  • 14
  • This `f:selectItem itemValue="#{null}"` indeed was a issue. Still, there is question how `f:selectItem itemValue=""` was working with Mojarra 2.2.X version? – Znas Me Sep 11 '17 at 09:18
  • This issue is also connected to this topic [link](https://stackoverflow.com/questions/17052503/using-a-please-select-fselectitem-with-null-empty-value-inside-a-pselectonem/). – Znas Me Sep 11 '17 at 09:26
  • Similar issue but with Primefaces and no JSF2.3 (which allows generics, but 2.2 does not). So if this solved your issue (by using `#{null}`) could you please mark my answer as solution? – Roland Sep 12 '17 at 21:24
  • @ZnasMe possibly an API change in Mojarra that they re-decided and rewrote it then (which is more clear code: `null` != `""` (empty string in Java code). – Roland Oct 01 '17 at 18:57
  • Please note that calling an EJB from within a converter does work but is not very good in performance as **every** id->object converting causes an EJB invocation which is "expensive" (serializing and un-serializing data on both directions by CORBA + JPA invocations). This can be boosted (not asked by topic starter) by letting the web application (also Swing, for sure) use a JS107 cache and the converter invoke that backing bean's method through CDI. – Roland Nov 02 '17 at 10:45
1

I had two issues with the FacesConverter.

First one, when I didn't use forClass = MyCustomObject.class I got the exact same error message as you had. Putting the forClass attribute in the @FacesConverter annotation solved the issue. It should solve it also for you...

Secondly, I suppose this is happening in the getAsString method? I had another issue with String casting and I had to do: return myCustomObject.getId().toString() where id is of type Long. After that I adapted my getAsObject to fetch the MyCustomObject based on the id. Maybe it is not directly related to your problem, but I thought it was important to mention as it is something I often bump into when writing converters.

onderbewustzijn
  • 935
  • 7
  • 32
  • If I use `forClass=MyCustomObject.class` it did work, but in my case it is not desired solution. Also, in `getAsString` method string is returned without casting. Therfore, solution when `value` is set on `@FacesConvertor` still does not work. I think this is issue with JSF/Mojarra/CDI integration. – Znas Me May 21 '17 at 22:02
  • 1
    Somehow someting goes wrong, either in your converter or either in the definition of your selectOneMenu. Could you post the code of the converter and also what's inside the selectonemenu? – onderbewustzijn May 22 '17 at 05:57
  • I voted this up because it is an alternative (`forClass` and not `value` attribute of the annotation) which does automatically register the converter everywhere the POJO (mostly) is used. But `myPojo.getField().toString()` looks a bit wrong to me as `getField()` may return `null` and then you get the (in)famous `NullPointerException` here. As shown in my answer, I use `String.valueOf(value.getField());` which is (when you look at the method's source code) null-safe. – Roland Sep 21 '17 at 19:02
  • A little off-topic but NPE-preventing: Use `Objects.equals(o1, o2)` instead of `o1.equals(o2)` as the first one does check for `null` reference for you then calls `o1.equals(o2)` for you. – Roland Sep 21 '17 at 19:04