Programatically Set Attribute with a Value Expression when component is within JSTL forEach loop - jsf

First time asking a question so please bear with me.
I have a pure Java custom component that extends UIInput (JSF 2.2, Mojarra) and I am using it like so:
<c:forEach items="#{bean.items}" var="item">
<my:component item="#{item}" />
</c:forEach>
I am trying to avoid unnecessarily specifying the 'value', 'valueChangeListener' and 'validator' attributes on the tag in the .xhtml file.
In my custom component I have overridden the setValueExpression method like so:
#Override
public void setValueExpression(String name, ValueExpression expression) {
super.setValueExpression(name, expression);
if ("item".equals(name)) {
this.setValue(Components.createValueExpression("#{item.myValue}", MyValue.class));
this.addValueChangeListener(new MethodExpressionValueChangeListener(Components.createVoidMethodExpression("#{item.myValueChanged}", ValueChangeEvent.class)));
this.addValidator(new MethodExpressionValidator(Components.createVoidMethodExpression("#{item.validateMyValue}", FacesContext.class, UIComponent.class, Object.class)));
}
}
I'm using OmniFaces there with its Components utility to reduce boilerplate code.
When it comes time to act on any of those three (validate on submit, for example) it results in:
javax.faces.FacesException: javax.el.PropertyNotFoundException: Target Unreachable, identifier 'item' resolved to null
I am pretty sure I know why, I just don't know what to do about it.
I believe when it comes time for the three expressions I am trying to set programatically to be resolved it's trying to find a bean in some scope by the name, 'item' however that doesn't exist since 'item' was a point in time variable within a JSTL forEach loop.
I think there's a special deferred kind of expression that Weld is using for item itself (which I can kinda see when I debug that setValueExpression method) that is aware or otherwise has references to that point in time variable but I'm not doing the same thing when I set up those three expressions and therefore there's no handle to that later on when it comes time for them to be resolved.
I am sure there is a way to wire this together I'm just not seeing it.
Additionally, I know I could just put the three attributes on the tag in the .xhtml like this:
<my:component item="#{item}" value="#{item.myValue}" valueChangeListener="#{item.myValueChanged}" validator="#{item.validateMyValue}" />
Then they would get their special deferred expressions just like item itself does (and indeed everything works as expected this way) but I'd rather not - it's something I'd have to repeat a lot and it just seems like there should be a way to do what I am trying above.

I believe I found the answer.
What I kept seeing in the debugger kept nagging at me, I just wasn't sure how to wire up the same scenario manually, then I found a Stack Overflow post with the following code snip at the very bottom:
VariableMapper varMapper = new DefaultVariableMapper();
varMapper.setVariable(mappingName, component.getValueExpression(mappedAttributeName));
return new ValueExpressionImpl(expression, null, null, varMapper, expectedType);
That was enough to point me in the right direction of re-using the incoming value expression for item itself in a VariableMapper instance which is in turn used to create the three value/method expressions so they each now have a handle & can resolve 'item' later on, when it comes time:
#Override
public void setValueExpression(String name, ValueExpression expression) {
super.setValueExpression(name, expression);
if ("item".equals(name)) {
VariableMapper varMapper = new DefaultVariableMapper();
varMapper.setVariable("item", expression);
ValueExpressionImpl valExprImpl = new ValueExpressionImpl("#{item.myValue}", null, null, varMapper, MyValue.class);
super.setValueExpression("value", valExprImpl);
MethodExpressionImpl meExprImpl = new MethodExpressionImpl("#{item.myValueChanged}", null, null, varMapper, Void.class, new Class<?>[] {ValueChangeEvent.class});
MethodExpressionValueChangeListener mevcl = new MethodExpressionValueChangeListener(meExprImpl);
this.addValueChangeListener(mevcl);
meExprImpl = new MethodExpressionImpl("#{item.validateMyValue}", null, null, varMapper, Void.class, new Class<?>[] {FacesContext.class, UIComponent.class, Object.class});
MethodExpressionValidator mev = new MethodExpressionValidator(meExprImpl);
this.addValidator(mev);
}
}
That seems to have done the trick (and looks so simple now...).

Related

PF Calendar in Dialog nulls Java Property in UiComponent#visitTree

Prerequisites:
xmlns:p="http://primefaces.org/ui"
javax.faces.component.UIComponent
Setup:
org.primefaces:primefaces:jar:6.1.2
org.glassfish:javax.faces:jar:2.2.14
Tomcat 8.5.28
Proprietary OR Mapper
#Property
private Calendar cal;
public Calendar getCal() {
return ORMapper.getProperty(this, "cal");
}
Functional setup:
p:commandButton that during its action executes code (that leads to the error) and then opens a dialogue which contains
<p:calendar id="cal" value="#{bean.model.cal}" />
Execution:
During Invoke Application of the commandButton (action) we traverse a section of the DOM tree that does NOT contain the dialogue (i.e. that dialogue is not a child of component) using
component.visitTree(
VisitContext.createVisitContext(
FacesContext.getCurrentInstance(),
null,
Collections.singleton(VisitHint.SKIP_UNRENDERED)),
(context, component) -> {
...
return VisitResult.ACCEPT;
}
);
Before the tree execution
model.cal = value
model.getCal() = value
Errorneous behaviour:
After the tree execution
model.cal = null
model.getCal() = value
This then leads to an error when validating the model which has a violation on e.g. #NotNull on the property (not the getter)
Notes:
Using IntelliJ attempts where made to find the change: Field Access/Modification breakpoints, calls to get/setter breakpoints, EL Method Invoker breakpoints
Removing the visitTree call fixes the Problem
Removing the element from the dialogue fixes the problem
Later on the value is set again, when couldnt be figured out either

change label value using value stored at session

i have two jsf pages (home.jsf and employees.jsf) ,
home page has a button that navigates to employees page,
while navigating i store value in session scope
at (Managed bean)
public void putSessionAL(ActionEvent actionEvent) {
FacesContext.getCurrentInstance().getExternalContext().getSessionMap().put("key","value");
}
public String navigate() {
return "employees";
}
i want to change Label at employees viewObject from UIHints tab depending on value stored at session using the following groovy expression
adf.context.sessionScope.key
and changed trustMode to trusted but it fires the following exception
oracle.jbo.script.ExprScriptException: JBO-29114 ADFContext is not setup to process messages for this exception. Use the exception stack trace and error code to investigate the root cause of this exception. Root cause error code is JBO-25188. Error message parameters are {0=Employees.FirstName, 1=, 2=oracle.jbo.script.ExprSecurityException}
at oracle.jbo.script.ExprScriptException.throwException(ExprScriptException.java:316)
at oracle.jbo.script.ExprScriptException.throwExceptionWithExprDef(ExprScriptException.java:387)
at oracle.jbo.ExprEval.processScriptException(ExprEval.java:599)
at oracle.jbo.ExprEval.doEvaluate(ExprEval.java:697)
at oracle.jbo.ExprEval.evaluate(ExprEval.java:508)
at oracle.jbo.ExprEval.evaluate(ExprEval.java:487)
at oracle.jbo.common.NamedObjectImpl.resolvePropertyRaw(NamedObjectImpl.java:680)
at oracle.jbo.server.DefObject.resolvePropertyRaw(DefObject.java:366)
One way to do it at the VO UIHint attribute label level will be programmaticaly by doing as follow :
In your VO go to the java tab and add the RowImpl java class
In the VORowImpl Add the following function
public String getMySessionLabel() {
return (String)FacesContext.getCurrentInstance().getExternalContext().getSessionMap().get("key");
}
In the Label add the following EL expression :
adf.object.getMySessionLabel()
This technique allow you more control than pure EL, if you want to do more than getting from session for example. In your case pure EL, as you did, should work as well. (Would need to check what is wrong with yours, maybe just missing the
#{adf.context.sessionScope.key}
If you attempt to get your label from a method in viewRowImpl. So this will be executed at least once for each row. I think this solution isn't fit for your case.
anyway ADF as a framework added strong policy and validations in EL in general and especially in version 12.2.x.
The solution for you case as following:
Create new class in model layer which extends oracle.jbo.script.ExprSecurityPolicy class
Override checkProperty method.
#Override
public boolean checkProperty(Object object, String string, Boolean b) {
if (object.getClass().getName().equals("oracle.adf.share.http.ServletADFContext") && string.equals("sessionScope")) {
return true;
}
return super.checkProperty(object, string, b);
}
Open adf-config.xml source and in startup tag set your class ExprSecurityPolicy property.
like:
<startup ExprSecurityPolicy="model.CustomExprSecurityPolicy">

Is it possible to debug for duplicate Ids?

I'm getting a duplicate id error but I can't figure out which element is meant.
java.lang.IllegalStateException: duplicate Id for a component pb_1_WAR_TEST_INSTANCE_1234_:_viewRoot:mainForm:tableOverview:j_id66
I know it has to be in tableOverview but the element with the ID:
j_id66 can not be found. When I search for it in my browser, I can only find elements with higher Id's like,
pb_1_WAR_TEST_INSTANCE_1234_:_viewRoot:mainForm:tableOverview:j_id67
Is there any way to find out which one is meant?
It's most likely a <h:column> or <rich:column>.
Quick way to find out the exact component is findComponent():
UIComponent component = context.getViewRoot().findComponent("pb_1_WAR_TEST_INSTANCE_1234_:_viewRoot:mainForm:tableOverview:j_id66");
System.out.println(component); // HtmlColumn?
Alternatively, just print out the JSF component tree. JSF 2.x offers <ui:debug> for this, in JSF 1.x you have to write custom code which traverses and prints UIViewRoot and all its children recursively. Here's a kickoff example:
public static void print(UIComponent component) {
System.out.println(component.getId() + " = " + component.getClass());
for (UIComponent child : component.getChildren()) {
print(child);
}
}
print(context.getViewRoot());
As to your concrete problem, it's most likely caused by binding a component to a bean which is not in the request scope. Don't do that.
See also:
How does the 'binding' attribute work in JSF? When and how should it be used?
Binding attribute causes duplicate component ID found in the view

org.hibernate.LazyInitializationException at com.sun.faces.renderkit.html_basic.MenuRenderer.convertSelectManyValuesForModel

Despite of FetchType.EAGER and JOIN FETCH, I get a LazyInitalizationException while adding some objects to a #ManyToMany collection via a JSF UISelectMany component such as in my case the <p:selectManyMenu>.
The #Entity IdentUser, with FetchType.EAGER:
#Column(name = "EMPLOYERS")
#ManyToMany(fetch = FetchType.EAGER, cascade= CascadeType.ALL)
#JoinTable(name = "USER_COMPANY", joinColumns = { #JoinColumn(name = "USER_ID") }, inverseJoinColumns = { #JoinColumn(name = "COMPANY_ID") })
private Set<Company> employers = new HashSet<Company>();
The #Entity Company, with FetchType.EAGER:
#ManyToMany(mappedBy="employers", fetch=FetchType.EAGER)
private List<IdentUser> employee;
The JPQL, with JOIN FETCH:
public List<IdentUser> getAllUsers() {
return this.em.createQuery("from IdentUser u LEFT JOIN FETCH u.employers WHERE u.enabled = 1 AND u.accountNonLocked=0 ").getResultList();
}
The JSF UISelectMany component causing the exception while submitting:
<p:selectManyMenu value="#{bean.user.employers}" converter="#{entityConverter}">
<f:selectItems value="#{bean.companies}" var="company" itemValue="#{company}" itemLabel="#{company.name}"/>
</p:selectManyMenu>
The relevant part of the stack trace:
org.hibernate.LazyInitializationException: failed to lazily initialize a collection, could not initialize proxy - no Session
at org.hibernate.collection.internal.AbstractPersistentCollection.throwLazyInitializationException(AbstractPersistentCollection.java:566)
at org.hibernate.collection.internal.AbstractPersistentCollection.withTemporarySessionIfNeeded(AbstractPersistentCollection.java:186)
at org.hibernate.collection.internal.AbstractPersistentCollection.initialize(AbstractPersistentCollection.java:545)
at org.hibernate.collection.internal.PersistentSet.add(PersistentSet.java:206)
at com.sun.faces.renderkit.html_basic.MenuRenderer.convertSelectManyValuesForModel(MenuRenderer.java:382)
at com.sun.faces.renderkit.html_basic.MenuRenderer.convertSelectManyValue(MenuRenderer.java:129)
at com.sun.faces.renderkit.html_basic.MenuRenderer.getConvertedValue(MenuRenderer.java:315)
at org.primefaces.component.selectmanymenu.SelectManyMenuRenderer.getConvertedValue(SelectManyMenuRenderer.java:37)
...
How is this caused and how can I solve it?
While submitting, the JSF UISelectMany components need to create a brand new instance of the collection with the submitted and converted values prefilled. It won't clear out and reuse the existing collection in the model as that may either get reflected in other references to the same collection, or may fail with an UnsupportedOperationException because the collection is unmodifiable, such as the ones obtained by Arrays#asList() or Collections#unmodifiableList().
The MenuRenderer, the renderer behind UISelectMany (and UISelectOne) components who's responsible for this all, will by default create a brand new instance of the collection based on collection's getClass().newInstance(). This would in turn fail with LazyInitializationException if the getClass() returns an implementation of Hibernate's PersistentCollection which is internally used by Hibernate to fill the collection property of an entity. The add() method namely needs to initialize the underlying proxy via the current session, but there's none because the job isn't performed within a transactional service method.
To override this default behavior of MenuRenderer, you need to explicitly specify the FQN of the desired collection type via the collectionType attribute of the UISelectMany component. For a List property, you'd like to specify java.util.ArrayList and for a Set property, you'd like to specify java.util.LinkedHashSet (or java.util.HashSet if ordering isn't important):
<p:selectManyMenu ... collectionType="java.util.LinkedHashSet">
The same applies to all other UISelectMany components as well which are directly tied to a Hibernate-managed JPA entity. E.g:
<p:selectManyCheckbox ... collectionType="java.util.LinkedHashSet">
<h:selectManyCheckbox ... collectionType="java.util.LinkedHashSet">
<h:selectManyListbox ... collectionType="java.util.LinkedHashSet">
<h:selectManyMenu ... collectionType="java.util.LinkedHashSet">
See also the VDL documentation of among others <h:selectManyMenu>. This is unfortunately not specified in VDL documentation of <p:selectManyMenu>, but as they use the same renderer for converting, it must work. If the IDE is jerking about an unknown collectionType attribute and annoyingly underlines it even though it works when you ignore'n'run it, then use <f:attribute> instead.
<p:selectManyMenu ... >
<f:attribute name="collectionType" value="java.util.LinkedHashSet" />
...
</p:selectManyMenu>
Solution: Replace the editUserBehavior.currentUser.employers with collection that is not managed by Hibernate.
Why? When the Entity becomes managed, the Hibernate replaces your HashSet with its own implementation of Set (be it PersistentSet). By analysing the implementation of JSF MenuRenderer, it turns out that at one point it creates new Set reflectively. See the comment in MenuRenderer.convertSelectManyValuesForModel()
// try to reflect a no-argument constructor and invoke if available
During construction of PersistentSet initialize() is invoked and - as this class is only meant to be invoked from Hibernate - LazyInitializationException is thrown.
Note: This is my suspicion only. I don't know your versions of JSF and Hibernate but this is more likely the case.

JSF expression evaluation of attribute tags

I have a problem with evaluating EL expressions containing vars created by other tags.
I have a project where I am using a custom validator.
public class MyValidator implements Validator, StateHolder
I have a tag class associated with it:
public class MyValidatorTag extends ValidatorTag
this class allows for attribute fieldName, with the appropriate tld file for the tag:
<tag>
<name>my-validator</name>
<tag-class>my.packaga.MyValidatorTag</tag-class>
<body-content>JSP</body-content>
<description>This is my validator</description>
<attribute>
<name>fieldName</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
<type>java.lang.String</type>
<description>This is some field I need</description>
</attribute>
</tag>
So far so good. But this setup doesn't allow one to use JSF EL expressions for attribute value.
So the validator class (not tag class) I use this code to evaluate attribute value:
public static String evaluateEl(String expression) {
String value = null;
if (expression == null) {
return "";
}
if ((expression.indexOf("#{") != -1)
&& (expression.indexOf("#{") < expression.indexOf('}'))) {
Object evaledValue =
FacesContext.getCurrentInstance().getApplication().createValueBinding(
expression).getValue(FacesContext.getCurrentInstance());
if (evaledValue != null) {
value = evaledValue.toString();
} else {
value = null;
}
} else {
value = expression;
}
return value;
}
It is limited to strings, and it works for most EL expressions. Expression Hello #{1 eq 2} will cause the attribute to have value Hello false.
But there is a case for which this will not work. Any expression which contains a var created by another tag doesn't work. Datatables, dataiterators and most notably <f:loadBundle>, e.g.
<f:loadBundle basename="mypackage.message.ui-strings" var="msgs" />
Followed by an input component containing validator:
<cust:my-validator fieldName="#{msgs['myfield1.name']}"></cust:my-validator>
Fieldname evaluates to empty string using the expression evaluation code above. Changing it to #{requestScope.msgs.... doesn't fix the problem. Looking at loadBundle implementation, it adds the var msgs to request scope, so I don't see why vars don't work for me. Please, somebody help me understand.
Any expression which contains a var created by another tag doesn't work. Datatables, dataiterators and most notably , e.g.
Tag handlers like <f:xxx>, <c:xxx> and some <ui:xxx> (those not having rendered attribute) are executed during JSF view build time, when a JSF UI component tree is to be produced. They are not part of the UI component tree. They have already done their job of producing the necessary JSF UI components.
UI components like <h:xxx> and some <ui:xxx> (those having rendered attribute) are executed during JSF view render time, when a large HTML string is to be produced which is to be sent to the HTTP response of the current HTTP request.
So they don't run in sync.
There are for the enduser of the validator several ways to go around this, all are listed in this answer: How to set converter properties for each row of a datatable?
For the developer, there's another solution, let the validator extend UIComponentBase instead and perform the job in processValidators(). The parent component is just available by UIComponent#getParent() and its submitted value is in turn available by UIInput#getSubmittedValue().

Resources