@Produces @ViewScoped @Named bean gets put into Viewscope with wrong name
I have a bean with several @Produces @Named("someName") @ViewScoped producers which all return ArrayLists. This caused ClassCastExceptions on postback where elements of one producers list could not be cast to the others. After debugging for hours I finally found the culprit. jsf-impl contains a com.sun.faces.application.view.ViewScopeContextManager.java with the following method:
/**
* Get the name of the bean for the given object.
*
* @param instance the object.
* @return the name.
*/
private String getName(Object instance) {
String name = instance.getClass().getSimpleName().substring(0, 1).toLowerCase()
+ instance.getClass().getSimpleName().substring(1);
Named named = instance.getClass().getAnnotation(Named.class);
if (named != null && named.value() != null && !named.value().trim().equals("")) {
name = named.value();
}
return name;
}
This gets called whenever instance is put into the ViewScope to find out it's name.
In my case, this returns 'arrayList', since it only looks for the @Named-annotation on the class (ArrayList), where there is no such annotation, obviously.
Repeated calls to createBean() will then overwrite the entry in the viewMap.
Calling code is here:
/**
* Create the bean.
*
* @param <T> the type.
* @param facesContext the faces context.
* @param contextual the contextual.
* @param creational the creational.
* @return the value or null if not found.
*/
public <T> T createBean(FacesContext facesContext, Contextual<T> contextual, CreationalContext<T> creational) {
if (LOGGER.isLoggable(Level.FINEST)) {
LOGGER.log(Level.FINEST, "Creating @ViewScoped CDI bean using contextual: {0}", contextual);
}
if (!(contextual instanceof PassivationCapable)) {
throw new IllegalArgumentException("ViewScoped bean " + contextual.toString() + " must be PassivationCapable, but is not.");
}
T result = contextual.create(creational);
if (result != null) {
String name = getName(result);
facesContext.getViewRoot().getViewMap(true).put(name, result);
String passivationCapableId = ((PassivationCapable)contextual).getId();
getContextMap(facesContext).put(passivationCapableId,
new ViewScopeContextObject(passivationCapableId, name));
}
return result;
}
(Code is from jsf-impl-2.2.13.SP4-redhat-1, but a quick lookup in the latest source reveals that latest 2.3 still has this code)
In createBean() I can get to the proper name with ((SerializableContextualFactory.PassivationCapableSerializableBean) contextual).getName(), which obviously cannot be used, but there should be a proper way to determine the correct name.
Thanks for the report.
This is another case of where Java EE code looks directly at classes and forgets that in CDI things are a bit more dynamic. Various interceptors that try to grab the annotation attributes (e.g. the ones from @Transactional) have the same issue if that annotation is dynamically added.
I'm not sure how to address this right now. May need some help from the CDI spec or in the meantime maybe some Weld and OWB specific helper code.
@BalusC What do you think?
@BalusC: This answers your question here.
Just ran into this bug. A simple produces class:
`public class Producers {
@Produces
@ViewScoped
@Named( "stringList" )
public List<String> producesStrings()
{
List<String> list = new ArrayList<>();
list.add( "testString" );
return list;
}
@Produces
@ViewScoped
@Named( "integerList" )
public List<Integer> producesInteger()
{
List<Integer> list = new ArrayList<>();
list.add( 1 );
return list;
}
} `
And xhtml:
<h2>Strings</h2> <ui:repeat var="var1" value="#{stringList}">#{var1}<br /> </ui:repeat> <h2>Integer</h2> <ui:repeat var="var2" value="#{integerList}">#{var2}<br /> </ui:repeat>
Load the page once and you get:
Strings testString Integer 1
refresh the page and you get
Strings testString Integer testString