<f:attribute value="#{some EL expression}"> not found via getAttributes().containsKey() - jsf

I cannot retrieve attributes that contain EL inside my ViewHandler.
As a minimal example, here's my ViewHandler. It just looks for an attribute "attributeName" and prints out its value.
public class MiniViewHandler extends ViewHandlerWrapper
{
private ViewHandler defaultViewHandler = null;
public MiniViewHandler()
{
}
public MiniViewHandler(ViewHandler defaultViewHandler)
{
super();
this.defaultViewHandler = defaultViewHandler;
}
#Override
public void renderView(FacesContext context, UIViewRoot viewToRender) throws IOException, FacesException
{
viewToRender.visitTree(VisitContext.createVisitContext(context),
new VisitCallback()
{
#Override
public VisitResult visit(VisitContext context, UIComponent target)
{
if (target.getAttributes().containsKey("attributeName"))
{
System.out.println("Found it: " + target.getAttributes().get("attributeName"));
}
return VisitResult.ACCEPT;
}
}
);
defaultViewHandler.renderView(context, viewToRender);
}
#Override
public ViewHandler getWrapped()
{
return defaultViewHandler;
}
}
This is registered in the faces-config.xml. The xhtml that I'm hitting:
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core">
<h:body>
<f:view>
<f:attribute name="attributeName" value="#{2 + 5}"/>
</f:view>
<f:view>
<f:attribute name="attributeName" value="2 + 5"/>
</f:view>
</h:body>
</html>
The logged output shows that only the non-EL attribute was picked up.
INFO [stdout] (http-/127.0.0.1:8080-1) Found it: 2 + 5

The mistake is here:
if (target.getAttributes().containsKey("attributeName"))
You're using containsKey() to check if the property has been specified. This however won't work if the attribute is a ValueExpression. Here's an extract from UIComponent#getAttributes() javadoc with emphasis:
getAttributes
public abstract java.util.Map<java.lang.String,java.lang.Object> getAttributes()
Return a mutable Map representing the attributes (and properties, see below) associated wth this UIComponent, keyed by attribute name (which must be a String). The returned implementation must support all of the standard and optional Map methods, plus support the following additional requirements:
The Map implementation must implement the java.io.Serializable interface.
Any attempt to add a null key or value must throw a NullPointerException.
Any attempt to add a key that is not a String must throw a ClassCastException.
If the attribute name specified as a key matches a property of this UIComponent's implementation class, the following methods will have special behavior:
containsKey() - Return false.
get() - If the property is readable, call the getter method and return the returned value (wrapping primitive values in their corresponding wrapper classes); otherwise throw IllegalArgumentException.
put() - If the property is writeable, call the setter method to set the corresponding value (unwrapping primitive values in their corresponding wrapper classes). If the property is not writeable, or an attempt is made to set a property of primitive type to null, throw IllegalArgumentException.
remove() - Throw IllegalArgumentException.
Thus, it always returns false for containsKey for component's properties (read: component's ValueExpressions). That's because dynamic properties are not stored in the attribute map, but instead in the component instance itself via UIComponent#setValueExpression() calls. They're only resolved when calling get().
You basically need to change the wrong line as follows, this also works for "static" attributes:
Object value = target.getAttributes().get("attributeName");
if (value != null) {
System.out.println("Found it: " + value);
}
If you would like to check if the attribute is actually being set, even though it would evaluate null like value="#{bean.returnsNull}", then you'd instead like to check if UIComponent#getValueExpression() doesn't return null.
if (target.getValueExpression("attributeName") != null) {
System.out.println("Found it: " + target.getAttributes().get("attributeName"));
}
This does in turn however not work for "static" attributes. You could just combine the checks if necessary, depending on the concrete functional requirements.

Related

j_idt5:client: Validation Error: Value is required [duplicate]

I have a problem with a p:selectOneMenu, no matter what I do I cannot get JSF to call the setter on the JPA entity. JSF validation fails with this message:
form:location: Validation Error: Value is not valid
I have this working on several other class of the same type (ie, join table classes) but cannot for the life of me get this one working.
If anyone can throw some troubleshooting/debugging tips for this sort of problem it would be greatly appreciated.
Using log statements I have verified the following:
The Conveter is returning correct, non null values.
I have no Bean Validation in my JPA entities.
The setter setLocation(Location location) is never called.
This is the simplest example I can do and it simply will not work:
<h:body>
<h:form id="form">
<p:messages id="messages" autoUpdate="true" />
<p:selectOneMenu id="location" value="#{locationStockList.selected.location}" converter="locationConverter">
<p:ajax event="change" update=":form:lblLocation"/>
<f:selectItems value="#{locationStockList.locationSelection}"/>
</p:selectOneMenu>
</h:form>
</h:body>
Converter:
#FacesConverter(forClass=Location.class, value="locationConverter")
public class LocationConverter implements Converter, Serializable {
private static final Logger logger = Logger.getLogger(LocationConverter.class.getName());
#Override
public Object getAsObject(FacesContext context, UIComponent component, String value) {
if (value.isEmpty())
return null;
try {
Long id = Long.parseLong(value);
Location location = ((LocationManagedBean) context.getApplication().getELResolver().getValue(context.getELContext(), null, "location")).find(id);
logger.log(Level.SEVERE, "Converted {0} to {1}" , new Object[] {value, location});
return location;
} catch (NumberFormatException e) {
return new Location();
}
}
#Override
public String getAsString(FacesContext context, UIComponent component, Object value) {
if (value == null || value.toString().isEmpty() || !(value instanceof Location))
return "";
return String.valueOf(((Location) value).getId());
}
}
Console output:
// Getter method
INFO: Current value=ejb.locations.Location[id=null, name=null, latitude=0.0, longitude=0.0]
// Session Bean
INFO: Finding ejb.locations.Location with id=3
// Session Bean
INFO: ### Returning : ejb.locations.Location[id=3, name=mdmd, latitude=4.5, longitude=2.3]
// Converter
SEVERE: Converted 3 to ejb.locations.Location[id=3, name=mdmd, latitude=4.5, longitude=2.3]
// Getter method -> Where did my selected Location go ??
INFO: Current value=ejb.locations.Location[id=null, name=null, latitude=0.0, longitude=0.0]
Validation fails with the message "form:location: Validation Error: Value is not valid"
This error boils down to that the selected item does not match any of the available select item values specified by any nested <f:selectItem(s)> tag during processing of the form submit request.
As part of safeguard against tampered/hacked requests, JSF will reiterate over all available select item values and test if selectedItem.equals(availableItem) returns true for at least one available item value. If no one item value matches, then you'll get exactly this validation error.
This process is under the covers basically as below, whereby bean.getAvailableItems() fictionally represents the entire list of available select items as defined by <f:selectItem(s)>:
String submittedValue = request.getParameter(component.getClientId());
Converter converter = component.getConverter();
Object selectedItem = (converter != null) ? converter.getAsObject(context, component, submittedValue) : submittedValue;
boolean valid = false;
for (Object availableItem : bean.getAvailableItems()) {
if (selectedItem.equals(availableItem)) {
valid = true;
break;
}
}
if (!valid) {
throw new ValidatorException("Validation Error: Value is not valid");
}
So, based on the above logic, this problem can logically have at least the following causes:
The selected item is missing in the list of available items.
The equals() method of the class representing the selected item is missing or broken.
If a custom Converter is involved, then it has returned the wrong object in getAsObject(). Perhaps it's even null.
To solve it:
Ensure that exactly the same list is been preserved during the subsequent request, particularly in case of multiple cascading menus. Making the bean #ViewScoped instead of #RequestScoped should fix it in most cases. Also make sure that you don't perform the business logic in the getter method of <f:selectItem(s)>, but instead in #PostConstruct or an action event (listener) method. If you're relying on specific request parameters, then you'd need to explicitly store them in the #ViewScoped bean, or to re-pass them on subsequent requests by e.g. <f:param>. See also How to choose the right bean scope?
Ensure that the equals() method is implemented right. This is already done right on standard Java types such as java.lang.String, java.lang.Number, etc, but not necessarily on custom objects/beans/entites. See also Right way to implement equals contract. In case you're already using String, make sure that the request character encoding is configured right. If it contains special characters and JSF is configured to render the output as UTF-8 but interpret the input as e.g. ISO-8859-1, then it will fail. See also a.o. Unicode input retrieved via PrimeFaces input components become corrupted.
Debug/log the actions of your custom Converter and fix it accordingly. For guidelines, see also Conversion Error setting value for 'null Converter' In case you're using java.util.Date as available items with <f:convertDateTime>, make sure that you don't forget the full time part in the pattern. See also "Validation Error: Value is not valid" error from f:datetimeConverter.
See also:
Our selectOneMenu wiki page
How to populate options of h:selectOneMenu from database?
Make multiple dependent / cascading selectOneMenu dropdown lists in JSF
If anyone can throw some troubleshooting/debugging tips for this sort of problem it would be greatly appreciated.
Just ask a clear and concrete question here. Do not ask too broad questions ;)
In my case I forgot to implement a correct get/set methods. It happened because I have changed a lot of attributes along the development.
Without a proper get method, JSF can´t recover your selected item, and happens what BalusC said at item 1 of his answer:
1 . The selected item is missing in the list of available items. This can happen if the list of available items is served by a request scoped bean which is not properly reinitialized on subsequent request, or is incorrectly doing the business job inside a getter method which causes it to return a different list in some way.
This can be a Converter Issue or else DTO issue.
Try to solve this, by adding hashCode() and equals() methods in your object DTO; In the above scenario you can generate these methods within the Location object class which indicate as the 'DTO' here.
Example:
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + (int) (id ^ (id >>> 32));
return result;
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Location other = (Location) obj;
if (id != other.id)
return false;
return true;
}
Please note that the above example is for an 'id' of type 'long'.

JSF 2.3 Custom converter with generics

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.
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.
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.

How can I access the content of something created with <ui:define> programmatically?

Where in the contexts can I find the information for something built with a <ui:define>? I want to access a page title that has been defined with <ui:define name="title">Some title</ui:define> in my bean.
To illustrate my question, I can access a variable defined with
<ui:param name="myVariable" value="This is my variable!"/>
by looking at the variable mapper in the EL context, like this
VariableMapper variableMapper = elContext.getVariableMapper();
String myVariable = variableMapper.resolveVariable("myVariable").getValue(elContext).toString();
This works for <ui:param>, but how is it done for <ui:define>?
This is not possible via standard API. Xtreme Biker has posted a brilliant trick whereby a "default" <ui:param> value is specified inside the <ui:insert> which would be overriden (and thus absent) when a <ui:define> is actually specified as answer on Test if ui:insert has been defined in the template client
A (hacky) alternative would be to create a custom taghandler for the job. The <ui:define>s are by their name collected in Map handlers field of the CompositionHandler taghandler class behind <ui:composition>. This is (unfortunately) implementation specific, Mojarra and MyFaces have their own implementations whereby Mojarra has named the field handlers and MyFaces _handlers.
As the field is just protected, cleanest would be to just extend the CompositionHandler taghandler class and expose at least the keyset in the apply() method as attribute of FaceletContext. However, as the CompositionHandler class itself is declared final, we can't subclass it. Therefore, we can't go around wrapping it as a delegate and use some reflection hackery to grab the field anyway.
Here's a kickoff example based on Mojarra which collects all declared <ui:define> handler names in a Map<String, Boolean> so that you can nicely use them in EL like so #{defined.foo ? '...' : '...'} respectively #{not defined.foo ? '...' : '...'}.
public class DefineAwareCompositionHandler extends TagHandlerImpl implements TemplateClient {
private CompositionHandler delegate;
private Map<String, Boolean> defined;
#SuppressWarnings("unchecked")
public DefineAwareCompositionHandler(TagConfig config) {
super(config);
delegate = new CompositionHandler(config);
try {
Field field = delegate.getClass().getDeclaredField("handlers");
field.setAccessible(true);
Map<String, DefineHandler> handlers = (Map<String, DefineHandler>) field.get(delegate);
if (handlers != null) {
defined = new HashMap<>();
for (String name : handlers.keySet()) {
defined.put(name, true);
}
}
}
catch (Exception e) {
throw new FaceletException(e);
}
}
#Override
public void apply(FaceletContext ctx, UIComponent parent) throws IOException {
ctx.setAttribute("defined", defined);
delegate.apply(ctx, parent);
}
#Override
public boolean apply(FaceletContext ctx, UIComponent parent, String name) throws IOException {
return delegate.apply(ctx, parent, name);
}
}
Register it as follows in your custom my.taglib.xml:
<tag>
<tag-name>composition</tag-name>
<handler-class>com.example.DefineAwareCompositionHandler</handler-class>
</tag>
You could make use of it as below:
<my:composition
xmlns="http://www.w3.org/1999/xhtml"
xmlns:f="http://xmlns.jcp.org/jsf/core"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
xmlns:my="http://example.com/ui"
>
<ui:insert name="foo">
...
</ui:insert>
<div class="#{defined.foo ? 'style1' : 'style2'}">
...
</div>
</my:composition>
Again, this is hacky (as it's implementation specific), I'd not recommend using it.
See also:
Custom Facelet component in JSF

SelectOneMenu:"Validation Error: Value is not valid" [duplicate]

I have a problem with a p:selectOneMenu, no matter what I do I cannot get JSF to call the setter on the JPA entity. JSF validation fails with this message:
form:location: Validation Error: Value is not valid
I have this working on several other class of the same type (ie, join table classes) but cannot for the life of me get this one working.
If anyone can throw some troubleshooting/debugging tips for this sort of problem it would be greatly appreciated.
Using log statements I have verified the following:
The Conveter is returning correct, non null values.
I have no Bean Validation in my JPA entities.
The setter setLocation(Location location) is never called.
This is the simplest example I can do and it simply will not work:
<h:body>
<h:form id="form">
<p:messages id="messages" autoUpdate="true" />
<p:selectOneMenu id="location" value="#{locationStockList.selected.location}" converter="locationConverter">
<p:ajax event="change" update=":form:lblLocation"/>
<f:selectItems value="#{locationStockList.locationSelection}"/>
</p:selectOneMenu>
</h:form>
</h:body>
Converter:
#FacesConverter(forClass=Location.class, value="locationConverter")
public class LocationConverter implements Converter, Serializable {
private static final Logger logger = Logger.getLogger(LocationConverter.class.getName());
#Override
public Object getAsObject(FacesContext context, UIComponent component, String value) {
if (value.isEmpty())
return null;
try {
Long id = Long.parseLong(value);
Location location = ((LocationManagedBean) context.getApplication().getELResolver().getValue(context.getELContext(), null, "location")).find(id);
logger.log(Level.SEVERE, "Converted {0} to {1}" , new Object[] {value, location});
return location;
} catch (NumberFormatException e) {
return new Location();
}
}
#Override
public String getAsString(FacesContext context, UIComponent component, Object value) {
if (value == null || value.toString().isEmpty() || !(value instanceof Location))
return "";
return String.valueOf(((Location) value).getId());
}
}
Console output:
// Getter method
INFO: Current value=ejb.locations.Location[id=null, name=null, latitude=0.0, longitude=0.0]
// Session Bean
INFO: Finding ejb.locations.Location with id=3
// Session Bean
INFO: ### Returning : ejb.locations.Location[id=3, name=mdmd, latitude=4.5, longitude=2.3]
// Converter
SEVERE: Converted 3 to ejb.locations.Location[id=3, name=mdmd, latitude=4.5, longitude=2.3]
// Getter method -> Where did my selected Location go ??
INFO: Current value=ejb.locations.Location[id=null, name=null, latitude=0.0, longitude=0.0]
Validation fails with the message "form:location: Validation Error: Value is not valid"
This error boils down to that the selected item does not match any of the available select item values specified by any nested <f:selectItem(s)> tag during processing of the form submit request.
As part of safeguard against tampered/hacked requests, JSF will reiterate over all available select item values and test if selectedItem.equals(availableItem) returns true for at least one available item value. If no one item value matches, then you'll get exactly this validation error.
This process is under the covers basically as below, whereby bean.getAvailableItems() fictionally represents the entire list of available select items as defined by <f:selectItem(s)>:
String submittedValue = request.getParameter(component.getClientId());
Converter converter = component.getConverter();
Object selectedItem = (converter != null) ? converter.getAsObject(context, component, submittedValue) : submittedValue;
boolean valid = false;
for (Object availableItem : bean.getAvailableItems()) {
if (selectedItem.equals(availableItem)) {
valid = true;
break;
}
}
if (!valid) {
throw new ValidatorException("Validation Error: Value is not valid");
}
So, based on the above logic, this problem can logically have at least the following causes:
The selected item is missing in the list of available items.
The equals() method of the class representing the selected item is missing or broken.
If a custom Converter is involved, then it has returned the wrong object in getAsObject(). Perhaps it's even null.
To solve it:
Ensure that exactly the same list is been preserved during the subsequent request, particularly in case of multiple cascading menus. Making the bean #ViewScoped instead of #RequestScoped should fix it in most cases. Also make sure that you don't perform the business logic in the getter method of <f:selectItem(s)>, but instead in #PostConstruct or an action event (listener) method. If you're relying on specific request parameters, then you'd need to explicitly store them in the #ViewScoped bean, or to re-pass them on subsequent requests by e.g. <f:param>. See also How to choose the right bean scope?
Ensure that the equals() method is implemented right. This is already done right on standard Java types such as java.lang.String, java.lang.Number, etc, but not necessarily on custom objects/beans/entites. See also Right way to implement equals contract. In case you're already using String, make sure that the request character encoding is configured right. If it contains special characters and JSF is configured to render the output as UTF-8 but interpret the input as e.g. ISO-8859-1, then it will fail. See also a.o. Unicode input retrieved via PrimeFaces input components become corrupted.
Debug/log the actions of your custom Converter and fix it accordingly. For guidelines, see also Conversion Error setting value for 'null Converter' In case you're using java.util.Date as available items with <f:convertDateTime>, make sure that you don't forget the full time part in the pattern. See also "Validation Error: Value is not valid" error from f:datetimeConverter.
See also:
Our selectOneMenu wiki page
How to populate options of h:selectOneMenu from database?
Make multiple dependent / cascading selectOneMenu dropdown lists in JSF
If anyone can throw some troubleshooting/debugging tips for this sort of problem it would be greatly appreciated.
Just ask a clear and concrete question here. Do not ask too broad questions ;)
In my case I forgot to implement a correct get/set methods. It happened because I have changed a lot of attributes along the development.
Without a proper get method, JSF can´t recover your selected item, and happens what BalusC said at item 1 of his answer:
1 . The selected item is missing in the list of available items. This can happen if the list of available items is served by a request scoped bean which is not properly reinitialized on subsequent request, or is incorrectly doing the business job inside a getter method which causes it to return a different list in some way.
This can be a Converter Issue or else DTO issue.
Try to solve this, by adding hashCode() and equals() methods in your object DTO; In the above scenario you can generate these methods within the Location object class which indicate as the 'DTO' here.
Example:
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + (int) (id ^ (id >>> 32));
return result;
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Location other = (Location) obj;
if (id != other.id)
return false;
return true;
}
Please note that the above example is for an 'id' of type 'long'.

Mojarra findComponent return null for valid id when inside composite component

I have a custom component that look as follow
<custom:container>
<custom:checkbox index="0"/>
<custom:checkbox index="1"/>
</custom:container>
so when encodeBegin first call, it will show hit the tag <custom:container>, and it will try to save this component cliend id,
private String containerClientId;
public void encodeBegin(FacesContext context, UIComponent component){
if (component instanceof ManyCheckboxContainer) {
containerClientId = component.getClientId(context);
return;
}
}
so encodeEnd get called when I hit <custom:checkbox index="0"/>, like this
public void encodeEnd(FacesContext context, UIComponent component) throws IOException {
...
if (component instanceof Checkbox) {
renderCheckbox(context, (Checkbox) component);
}
...
}
protected void renderCheckbox(FacesContext facesContext, InforRadio radio) throws IOException {
...
UIComponent uiComponent = radio.findComponent(containerClientId);
if(uiComponent == null){
//throw error
}
...
}
If I DO NOT have this custom component inside composite component then everything work great, but once I put it in a composite component, radio.findComponent(containerClientId); return null. Is this a bug in mojarra? I test this under 2.1.10 and 2.1.11, same behavior.
EDIT
So I take that back, this behavior happen when my custom component is inside two nested NamingContainer, so something like this
<h:form id="myForm">
<f:subView id="myView">
<custom:container id="myCustom">
<custom:checkbox index="0"/>
<custom:checkbox index="1"/>
</custom:container>
</f:subView>
</h:form>
so in this case the client id (that return by component.getClientId(context)) for <custom:container> is myForm:myView:myCustom, but inside Mojarra, the findComponent method has this
public UIComponent findComponent(String expr) {
...
else if (!(base instanceof NamingContainer)) {
// Relative expressions start at the closest NamingContainer or root
while (base.getParent() != null) {
if (base instanceof NamingContainer) {
break;
}
base = base.getParent();
}
...
}
so it looks for the next ancestor NamingContainer, which in my case is the f:subView not the h:form. It then parse the client id, loop through it, each time passing piece of the id to the UIComponent findComponent(UIComponent base, String id, boolean checkId). So the first time in, this method take form3 as id and current UIComponent is f:subView, it search for all its facets and children to see if any component match form3, of course, none will match since form3 is the parent of f:subView in my structure. So null is return. Is this Mojarra bugs or am I doing some wrong. I thought that the client id is relative from the next NamingContainer ancestor instead of all the way to the root of the NamingContainer? Am I wrong on that?
After reading the docs it seems to me that Mojarra got it right.
According to the docs you should place a seperator (a colon) in front of your search expression if you want to do an "absolute" search from the root of your tree.
Otherwise it will do a relative search from the component itself if it is a NamingContainer or the first parent that is a NamingContainer.
BTW: The docs I linked to appear to be the same as the official docs distributed with the specs. Official specs are here.

Resources