I'm writing a validator method in JSF 2. I have a ui:repeat element in my page that renders a list of items. Each item has a date property, and I need to ensure the dates correspond to each other in a specific sequence, e.g. the date of the last item in the list doesn't come before the date of the first item. I was trying to get all the child elements inside the ui:repeat and iterate over them to do the comparison, but I don't really know where to start. I've seen how to get a specific element by ID:
UIInput input = (UIInput) context.getViewRoot().findComponent(elementId);
However, within the ui:repeat the ID values are made unique by JSF, so I don't know what they are at compile time. Furthermore, at compile time I don't know how many items there will be in the list.
I've looked at the Javadoc for UIViewRoot and other associated classes, and have tried a couple things, but I'm getting errors, things aren't working, and I don't really know if I'm even close to getting anywhere. I'm leaving the code of my attempts out of this post, becuase they're probably a joke.
There's physically only one UIInput component whose state changes depending on the current iteration round of UIRepeat. It's available by just its client ID without the UIRepeat index: findComponent("formId:inputId") (the UIRepeat index is only of significance in the client side). However, when the component is programmatically been accessed outside the context of UIRepeat this way, then it'll indeed return a seemingly empty state.
In order to visit the UIInput component in all those states as they are inside the UIRepeat and collect their values, you need to run UIComponent#visitTree() on the UIRepeat.
Here's a kickoff example:
<ui:repeat value="#{bean.items}" var="item">
<f:event type="postValidate" listener="#{bean.validateOrder}" />
<h:inputText value="#{item.value}" />
</ui:repeat>
With this validateOrder() method (again, just a kickoff example, this approach naively assumes that there's only one UIInput component in the repeater):
#SuppressWarnings("rawtypes")
public void validateOrder(ComponentSystemEvent event) {
final FacesContext context = FacesContext.getCurrentInstance();
final List<Comparable> values = new ArrayList<Comparable>();
event.getComponent().visitTree(VisitContext.createVisitContext(context), new VisitCallback() {
#Override
public VisitResult visit(VisitContext context, UIComponent target) {
if (target instanceof UIInput) {
values.add((Comparable) ((UIInput) target).getValue());
}
return VisitResult.ACCEPT;
}
});
boolean ordered = new ArrayList<Comparable>(new TreeSet<Comparable>(values)).equals(values);
if (!ordered) {
event.getComponent().visitTree(VisitContext.createVisitContext(context), new VisitCallback() {
#Override
public VisitResult visit(VisitContext context, UIComponent target) {
if (target instanceof UIInput) {
((UIInput) target).setValid(false);
}
return VisitResult.ACCEPT;
}
});
context.validationFailed();
context.addMessage(null, new FacesMessage("Values are not in order!"));
}
}
Note that it visits the tree twice; first time to collect the values and second time to mark those inputs invalid. Also note that this very specific requirement can't be done with a standard JSF validator. You can't attach a <f:validator> on <ui:repeat>. Attaching it on <h:inputText> is theoretically possible, but it would cause the very same validator to run as many times as the amount of repeated items, which doens't make sense. Also, the validator would need to take getSubmittedValue() vs getValue() into account this way.
OmniFaces has an <o:validateOrder> component which does a similar thing on fixed components, but it isn't designed for usage in dynamically repeated components.
Related
I am attempting to add primefaces to an existing Spring/JSF (myfaces) project. I have the actual timeline component working (or at least loading with the correct data), but the event listeners don't seem to work correctly. I have tried a number of configurations but can't seem to resolve this issue.
<p:timeline id="timeline1"
value="#{timelineView.getModel(searchFacade.dataModel)}"
editable="true" eventMargin="10" eventMarginAxis="0"
start="#{timelineView.start}"
end="#{timelineView.end}"
showNavigation="true" showButtonNew="true"
axisOnTop="true" stackEvents="false"
oncomplete="styleEvents();">
<p:ajax event="changed" listener="#{timelineView.onEdit()}" oncomplete="restyleTimeline()" />
</p:timeline>
In the above code the changed event is triggered and there is an AJAX call made. In the network pane of the browser I can see that the AJAX call is always to the URL of the current page (so like /mypage.jsf?conversationCode=a - I'm also using Apache Orchestra obviously)
The actual form data being sent for the changed event looks like this:
javax.faces.partial.ajax:true
javax.faces.source:timeline1
javax.faces.partial.execute:timeline1
javax.faces.behavior.event:changed
javax.faces.partial.event:changed
timeline1_eventIdx:0
timeline1_startDate:1498881600000
timeline1_endDate:1510894800000
timeline1_group:<div class='timeline-group'><div class='timeline-row timeline-row-group'>Group One/div><div class='timeline-row timeline-row-sub'>Group One subtitle</div><div class='timeline-row timeline-row-details'>Group One details</div></div>
mainForm:j_id_mt_SUBMIT:1
javax.faces.ViewState: Big hash as usual
The bizarrely long group name is because I am formatting the output group names into a more detailed title for the row btw.
The bean in the back (imports and package omitted):
public class TimelineView<T> implements Serializable {
private static final long serialVersionUID = 1L;
TimelineView<T> timelineView;
public TimelineModel getModel() {
return timelineView.getModel();
}
public TimelineModel getModel(ListDataModel<? extends Row<T>> results) {
return timelineView.getModel(results);
}
public void setTimelineView(TimelineView<T> timelineView) {
this.timelineView = timelineView;
}
public Date getStart() {
return timelineView.getStart();
}
public Date getEnd() {
return timelineView.getEnd();
}
public void onEdit(TimelineModificationEvent e) {
timelineView.onEdit(e);
}
public void onSelect(TimelineSelectEvent e) {
timelineView.onSelect(e);
}
}
The functionality and code inside these methods doesn't matter because when I set a breakpoint I can see that the onEdit method is never called and neither was the onSelect method when I had a select event in the timeline.
Since I have Spring handling all beans, my configuration was just this, located in the ApplicationContext.xml file:
<bean id="timelineView" class="com.mycompany.projectone.view.timeline.TimelineView" />
I have tried adding a form around the timeline, which made no difference, and have also tried adding process=":form_id", which caused the error "Cannot find component for expression ":form_id"".
As an alternative that would also suit my needs, does anyone know how to send primefaces events to a javascript function to be handled? For example, if the user moves the start and end points of the item in the timeline I would like to update the displayed start and end dates.
I would also like to change or intercept the delete behavior and modify what occurs when the delete link is clicked.
Any help would be hugely appreciated. Thanks.
EDIT
The suggested potential duplicate does not address the symptoms nor the actual solution - see answer below.
Okay, so the answer turns out to be that the Primfaces timeline component must have a widgetVar set in order for the p:ajax and pe:javascript calls to work. Incredibly simple solution but so infuriatingly difficult to find.
I have a lot of code with standard required validation.
Something like this
<h:inputText required="true" requiredMessage="it is required!"/>
Now, I need to change behavior of RequiredValidator: put error message to the context, but do not interrupt cycle. May be to do something more.
I tried to add custom validator with the same id, but it did not work.
<validator>
<validator-id>javax.faces.Required</validator-id>
<validator-class>my.RequiredValidator</validator-class>
</validator>
Is it possible?
Empty fields are not committed to validators. I could not find a matching documentation, but I tested the environment and please take a look at Get empty strings from <h:inputText> go through validation.
This question also handles an empty string input and BalusC says:
JSF 1.x does by default not fire validators on empty fields.
OK, it's about JSF 1.X, but it seems it didn't change.
Validators are to interrupt the request, but that's not what you want. So could it be a workaround to use the setter method for your needs?
Ommit the required attribute from your inputText and add a FacesMessage in the setter for your textInput. Assumed the assingned value is #{myBean.description}, the setter may look like this:
public void setDescription(String value) {
String _tmp = value.trim();
if (_tmp.equals("")) {
FacesContext ctx = FacesContext.getCurrentInstance();
ctx.addMessage(null, new FacesMessage(FacesMessage.SEVERITY_ERROR
, "Error:", "Input must not be empty"));
this.description = null;
return;
}
this.description = _tmp;
}
I am looking for a possibility to write programmatically ajax call on every element.
I have to ways, to build the UI Components
first - panelGroup binding- )
HtmlSelectOneMenu HSOM = new HtmlSelectOneMenu();
UISelectItems items = new UISelectItems();
List<SelectItem> comboList = new ArrayList<SelectItem>();
comboList.add(new SelectItem(" "));
comboList.add(new SelectItem("1"));
comboList.add(new SelectItem("2"));
comboList.add(new SelectItem("3"));
items.setValue(comboList);
HSOM.getChildren().add(items);
HSOM.setValueExpression("value", buildValueExpression("#{productDetails.productOptionValue}"));
AjaxBehavior ajax = new AjaxBehavior();
ajax.setValueExpression("value", buildValueExpression("#{productDetails.updateProduct()}"));
HSOM.addClientBehavior("valueChange", ajax);
HSOM.addValidator(new BeanValidator());
productOptions.getChildren().add(HSOM);
private ValueExpression buildValueExpression(String exp) {
FacesContext facesInstance = FacesContext.getCurrentInstance();
Application application = facesInstance.getApplication();
ExpressionFactory expressionFactory = application.getExpressionFactory();
String expression = exp;
return expressionFactory.createValueExpression(facesInstance.getELContext(), expression, String.class);
}
I can see, that a Ajax Call is linked to the component, but the updateProduct() function did not get called.
the other possibility to create the dynamic components is)
public void encodeEnd(FacesContext context) throws IOException {
System.out.println("Start encoding");
ResponseWriter responseWriter = context.getResponseWriter();
responseWriter.startElement("span", null);
responseWriter.writeAttribute("id",getClientId(context),"id");
responseWriter.writeAttribute("name", getClientId(context),"clientId");
responseWriter.write("Farbe");
responseWriter.endElement("span");
responseWriter.startElement("select", null);
responseWriter.writeAttribute("id",getClientId(context),"id");
responseWriter.writeAttribute("name", getClientId(context),"clientId");
responseWriter.writeAttribute("value", "#{artikelDetails.productOptionValue}", "value");
responseWriter.startElement("option", null);
responseWriter.write("Gelb");
responseWriter.endElement("option");
responseWriter.startElement("option", null);
responseWriter.write("Blau");
responseWriter.endElement("option");
responseWriter.endElement("select");
System.out.println("End encoding");
}
How to add a ajax call on every select ele here ?
And which of both method's do you prefer ?
This is a very simple example, where i do not build lot of select ele via loop
first i need to get this work...
You need to give all programmatically created input and command components a fixed ID, so that JSF can find the desired submitted information in the request parameter map. Otherwise they end up getting an autogenerated ID which is different during postback.
In your case, that's thus:
HSOM.setId("someId");
And which of both method's do you prefer ?
None of both. I'm confident that Java is the wrong tool for the purpose of declaring components in the view. JSF already ships with Facelets out the box which allows declaring components in a much easier and cleaner way by XML means. If you intend to build the view dynamically based on some preconditions, look at JSTL. See also among others How to make a grid of JSF composite component? and JSTL in JSF2 Facelets... makes sense?
I am busy writing my own JSF2 UIComponents and their relevant renderers. All of my UIComponents implements ClientBehaviorHolder. What I don't understand is how to really render ClientBehaviorHolder.
For example, the following code illustrates how ClientBehaviorHolder is rendered in Mojarra.
private static void renderHandler(FacesContext context,
UIComponent component,
Collection<ClientBehaviorContext.Parameter> params,
String handlerName,
Object handlerValue,
String behaviorEventName,
String submitTarget,
boolean needsSubmit,
boolean includeExec)
throws IOException {
ResponseWriter writer = context.getResponseWriter();
String userHandler = getNonEmptyUserHandler(handlerValue);
List<ClientBehavior> behaviors = getClientBehaviors(component, behaviorEventName);
// Don't render behavior scripts if component is disabled
if ((null != behaviors) &&
(behaviors.size() > 0) &&
Util.componentIsDisabled(component)) {
behaviors = null;
}
if (params == null) {
params = Collections.emptyList();
}
String handler = null;
switch (getHandlerType(behaviors, params, userHandler, needsSubmit, includeExec)) {
case USER_HANDLER_ONLY:
handler = userHandler;
break;
case SINGLE_BEHAVIOR_ONLY:
handler = getSingleBehaviorHandler(context,
component,
behaviors.get(0),
params,
behaviorEventName,
submitTarget,
needsSubmit);
break;
case SUBMIT_ONLY:
handler = getSubmitHandler(context,
component,
params,
submitTarget,
true);
break;
case CHAIN:
handler = getChainedHandler(context,
component,
behaviors,
params,
behaviorEventName,
userHandler,
submitTarget,
needsSubmit);
break;
default:
assert(false);
}
writer.writeAttribute(handlerName, handler, null);
}
For submit handlers, Mojarra adds the mojarra.jsfcljs javascript, UIParameters and other scripts. For chain handlers, jsf.util.chain is used.
My question is:
How does one determine if we have to render handlers in chain or a single behaviour or user specific handler?
mojarra.jsfcljs is only unique to Mojarra. PrimeFaces have their own implementation, so does Apache Tomahawk. Question is: what does mojarra.jsfcljs do and what is its use? This is so that I can write one for my own? Also, where can I find the implementation of mojarra.jsfcljs?
What is the specification to render ClientBehaviorHolder?
My sincere thanks in advance.
How does one determine if we have to render handlers in chain or a single behaviour or user specific handler?
Imagine that the enduser (read: the JSF developer who's using your component) programmed:
<your:component onclick="return foo()" />
And you intented to ultimately render for your component's own purpose:
<someHtmlElement onclick="jsf.ajax.request(...); return false;" />
Then you can't just concatenate the enduser's onclick in front of your component's jsf.ajax.request() like so
<someHtmlElement onclick="return foo(); jsf.ajax.request(...); return false;" />
Even if it returned true, your component's jsf.ajax.request won't be invoked at all. You ultimately want to end up something like:
<someHtmlElement onclick="if returnsTrue('return foo();') { jsf.ajax.request(...); } return false;" />
That's exactly what jsf.util.chain() is doing under the covers.
mojarra.jsfcljs is only unique to Mojarra. PrimeFaces have their own implementation, so does Apache Tomahawk. Question is: what does mojarra.jsfcljs do and what is its use? This is so that I can write one for my own? Also, where can I find the implementation of mojarra.jsfcljs?
It's inside the jsf.js file. Easy way to find it is to open a JSF page with <f:ajax> embedded and look in the generated <head> source for the <script> with its URL. This file is by default minified. If you set javax.faces.PROJECT_STAGE context param to Development, then this will be served unminified. The task of the jsfcljs() function is to submit the parent form with the necessary parameters. Here's an extract of relevance coming from Mojarra 2.1.21.
/*
* This is called by command link and command button. It provides
* the form it is nested in, the parameters that need to be
* added and finally, the target of the action. This function
* will delete any parameters added <em>after</em> the form
* has been submitted to handle DOM caching issues.
*
* #param f - the target form
* #param pvp - associative array of parameter
* key/value pairs to be added to the form as hidden input
* fields.
* #param t - the target of the form submission
*/
mojarra.jsfcljs = function jsfcljs(f, pvp, t) {
What is the specification to render ClientBehaviorHolder?
Use ClientBehavior#getScript() to get the autogenerated script. It requires a ClientBehaviorContext argument which can be created using ClientBehaviorContext#createClientBehaviorContext(). It's in turn your responsibility to render it into the appropriate HTML attribute, such as onclick.
FacesContext context = FacesContext.getCurrentInstance();
UIComponent inputOrCommandComponent = ...; // Your component.
String event = "click"; // Just the particular HTML DOM event name you need to listen on.
ClientBehaviorContext clientBehaviorContext = ClientBehaviorContext.createClientBehaviorContext(context, component, event, component.getClientId(context), null);
StringBuilder builder = new StringBuilder();
for (ClientBehavior behavior : component.getClientBehaviors().get(event)) { // Collect all <f:ajax> declarations on the given event.
builder.append(behavior.getScript(clientBehaviorContext));
builder.append(';');
}
String script = builder.toString();
// Write it to the desired HTML attribute.
Note that you absolutely don't need to worry about writing JSF implementation specific scripts this way. They will be generated for you.
All with all, ClientBehaviorHolder is just an abstraction of ajax support. It allows developers to nest <f:ajax> in your component. All standard JSF UIInput and UICommand components implement it.
I have a JSF2 application that renders a large table with complex content. Unfortunately, each request takes up to 6 seconds to process. Using simple debug output inside a phase listener, I could see that the performance loss distributes evenly over all phases that process the component tree. So I started up a profiler to see what's going on in detail and found out that over 300.000 ValueExpressions are evaluated during one simple request.
They resolve to really simple getters without any logic, so the problem is not executing the code behind these expressions but parsing the expression string and invoking the getter methods. This leads to a few questions:
1.) Is there any way to speed up the resolving of method expressions. Maybe a hidden "enable caching" flag or something.
2.) It seems most of the expressions are evaluated not inside the render response phase, where they are actually needed, but during the other phases. It seems unnecessary to resolve for example styleClass during any other phase than the render phase. Can I prevent this?
3.) Of course, minimizing the number of EL expressions in my facelets page should help getting more performance, but it seems that I cannot really do this: Many attributes (like the styleClass example mentioned above) are actually dependent on the table row, but can only be set on the column. So, having 10 columns, each expression is evaluated much too often. I've seen examples where the rowClasses attribute of the table is used to conditionally style the rows, but as the table is sortable, that won't work without rolling my own sorting mechanism. Is there a better way to implement this?
4.) One more simple question: Is there a way to cache variables in the component tree (just like ui:repeat provides access to the contents of a list and resolves the expression to get the list only once, but just for one variable)?
Thank you very much for all answers and hints!
EDIT:
After further investigation, I found out that for each rendered=#{someExpression}, the expression is evaluated 6 times per row just during the render response phase. I know that JSF may call my getters more than once, but I thought this would be because they can be called inside each phase. During rendering, that values shouldn't change, so I guess they could be cached.
Stepping through the code in the debugger, it looks like javax.faces.component.ComponentStateHelper (which appears in each of the stack traces leading to the evaluated method call) provides a map to do exactly this kind of caching. However, this doesn't seem to work as I expect and always re-evaluates the expression...
1.) Is there any way to speed up the resolving of method expressions. Maybe a hidden "enable caching" flag or something.
No one comes to mind.
2.) It seems most of the expressions are evaluated not inside the render response phase, where they are actually needed, but during the other phases. It seems unnecessary to resolve for example styleClass during any other phase than the render phase. Can I prevent this?
This should as far as I know not happen. The only ones which could/should be resolved before render response are rendered, required, disabled, readonly and value.
3.) Of course, minimizing the number of EL expressions in my facelets page should help getting more performance, but it seems that I cannot really do this: Many attributes (like the styleClass example mentioned above) are actually dependent on the table row, but can only be set on the column. So, having 10 columns, each expression is evaluated much too often. I've seen examples where the rowClasses attribute of the table is used to conditionally style the rows, but as the table is sortable, that won't work without rolling my own sorting mechanism. Is there a better way to implement this?
You could hand over the styling work to a smart piece/combination of JS/CSS.
4.) One more simple question: Is there a way to cache variables in the component tree (just like ui:repeat provides access to the contents of a list and resolves the expression to get the list only once, but just for one variable)?
Use JSTL <c:set>. I am not sure how that would affect after all, but you're then basically only moving the problem to somewhere else. A #{variableName} would still have a cost of locating it in any of the scopes. You can also consider to name the scope explicitly when accessing the variable. E.g. #{sessionScope.beanname} which should skip unnecessarily scanning in page and request scopes.
I know this one is kinda old but I want to add that this issue has been solved by the MyFaces implementation. It is documented in their Wiki: https://cwiki.apache.org/confluence/display/MYFACES/Cache+EL+Expressions
If you are using the mojarra reference implementation on glassfish you might try a nightly build as mentioned in this blog post by Ed Burns. There were some performance improvements contributed by the oracle adf developers relating to el expression evaluation.
Not sure if this is related, but you might also try to disable partial state saving by setting the init parameter javax.faces.PARTIAL_STATE_SAVING to false.
I suffer from repetitive getter calls on managed bean when using composite component, especially rendered getter is called million times. I do not know whether you use them too but I would like your opinion on my cacheing solution.
I succeeded with some cacheing behavior when I followed BalusC's backing component hint in the question Binding a managed bean instance to composite component.
Composite component:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:composite="http://java.sun.com/jsf/composite"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:ui="http://java.sun.com/jsf/facelets">
<!-- INTERFACE -->
<composite:interface componentType="fieldComponentType">
<composite:attribute name="id" type="java.lang.String" required="true" />
<composite:attribute name="label" type="java.lang.String" required="true"/>
<composite:attribute name="toBeRendered" type="java.lang.Boolean" required="true" />
<composite:attribute name="currentValue" required="true" />
</composite:interface>
<!-- IMPLEMENTATION -->
<composite:implementation>
<h:panelGrid rendered="#{cc._toBeRendered}" columns="3">
<h:outputText value="#{cc._label}:"/>
<h:inputText id="#{cc.attrs.id}" rendered="#{cc._toBeRendered}"
value="#{cc.attrs.currentValue}" />
</h:panelGrid>
</composite:implementation>
</html>
Backing component which provides caching within one phase:
package cz.kamosh;
import javax.faces.component.FacesComponent;
import javax.faces.component.NamingContainer;
import javax.faces.component.UIComponentBase;
import javax.faces.context.FacesContext;
#FacesComponent(value = "fieldComponentType")
public final class FieldComponentType extends UIComponentBase implements NamingContainer {
static class Setting {
String label;
Boolean toBeRendered;
#Override
public String toString() {
return "Setting [label=" + label + ", toBeRendered=" + toBeRendered
+ "]";
}
}
int lastPhaseId = -1;
Setting currentSetting = null;
public FieldComponentType() {
System.out.println("Constructor FieldComponentType");
}
#Override
public String getFamily() {
return "javax.faces.NamingContainer";
}
// Must be named with prefix _, otherwise infinite loop occurs
public String get_label() {
Setting setting = getSetting();
if (setting.label == null) {
setting.label = (String) getAttributes().get("label");
}
return setting.label;
}
// Must be named with prefix _, otherwise infinite loop occurs
public boolean is_toBeRendered() {
Setting setting = getSetting();
if (setting.toBeRendered == null) {
setting.toBeRendered = (Boolean) getAttributes().get("toBeRendered");
}
return setting.toBeRendered;
}
private Setting getSetting() {
int phaseId = FacesContext.getCurrentInstance().getCurrentPhaseId()
.getOrdinal();
if (currentSetting == null || phaseId > lastPhaseId) {
currentSetting = new Setting();
lastPhaseId = phaseId;
}
return currentSetting;
}
}
Testing page:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:util="http://java.sun.com/jsf/composite/components">
<h:head>
<title>Testing page</title>
</h:head>
<h:body>
<h:form>
<h:panelGrid>
<util:fieldComponent id="id3"
label="#{testingBean.label}"
toBeRendered="#{testingBean.toBeRendered}"
currentValue="#{testingBean.myValue}" />
</h:panelGrid>
<h:commandButton value="Do something" actionListener="#{testingBean.doSomething}" />
</h:form>
</h:body>
</html>
Managed bean:
package cz.kamosh;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.ViewScoped;
#ViewScoped
#ManagedBean(name="testingBean")
public class TestingBean {
private String myValue;
public String getMyValue() {
System.out.printf("getMyValue: %1$s\n", myValue);
return myValue;
}
public void setMyValue(String myValue) {
System.out.printf("setMyValue: %1$s\n", myValue);
this.myValue = myValue;
}
public void doSomething() {
System.out.printf("Do something, myValue: %1$s\n", this.myValue);
}
public String getLabel() {
System.out.printf("getLabel\n");
return "My value lbl";
}
public boolean isToBeRendered() {
System.out.printf("isToBeRendered\n");
return true;
}
}
After being profiled using jvisualvm 26 calls of com.sun.faces.facelets.el.TagValueExpression.getValue decreased from 26% to 16% of overall time spent in one complete request 'run' (compared with composite component which does not make use of componentType="fieldComponentType" - sources not included in this answer).
But anyway overhead of JSF2 framework itself costs about 80% of time compared to 20% spent in my code even if I call some database fetches (this is my experience from our production code). And I consider this overhead quite big :-(
#FRoothowe you mentioned that "During rendering, that values shouldn't change, so I guess they could be cached." From this point of view I can afford cacheing tag expression values within each phase, am I right?
After couple of hours of debugging I decided to improve ComponentStateHelper which is part of Mojarra JSF2 implementation.
I took the last version 2.1.4 https://maven.java.net/content/repositories/releases/org/glassfish/javax.faces/2.1.4/javax.faces-2.1.4-sources.jar
The main idea is to preserve results of EL expressions evaluated during each phase. I am still assuming that result of EL expression must be the same within one phase.
Changes in class javax.faces.component.ComponentStateHelper:
class ComponentStateHelper implements StateHelper , TransientStateHelper {
...
// Own cache for method public Object eval(Serializable key, Object defaultValue) {
int lastPhaseId = -1; // Last cached phase
private Map<Serializable, Object> evalCache;
...
public ComponentStateHelper(UIComponent component) {
...
// Instantiate own cache
this.evalCache = new HashMap<Serializable, Object>();
}
...
/**
* #see StateHelper#eval(java.io.Serializable, Object)
*/
public Object eval(Serializable key, Object defaultValue) {
Object retVal = get(key);
if (retVal == null) {
// Value evaluated and returned within one phase should be hopefully still same
int currentPhaseId = FacesContext.getCurrentInstance().getCurrentPhaseId().getOrdinal();
if(lastPhaseId < currentPhaseId) {
// Probably stale cache, so clear it to get fresh results
// in current phase
evalCache.clear();
lastPhaseId = currentPhaseId;
}
retVal = evalCache.get(key);
if(retVal == null) {
ValueExpression ve = component.getValueExpression(key.toString());
if (ve != null) {
retVal = ve.getValue(component.getFacesContext().getELContext());
}
}
// Remember returned value in own cache
evalCache.put(key, retVal);
}
return ((retVal != null) ? retVal : defaultValue);
}
...
}
This enhancement seems to be functional and number of calls to my managed bean decreased drastically, especially rendered getters which were called multiple times for the same component.
I maybe do not see what disasterous consequences this enhancement may cause. But if this is feasable I wonder why JSF guys have not used this type of cacheing.
*EDIT: This solution cannot be used as it has problems when combined with DataModel!!! *