Concurrent ajax-requests and synchronization of the view sta
jan.ziegler Oct 26, 2007 4:32 AMI´m faced with a problem that concurrent ajax-request to the same view-context seems to lead to synchronization problems with the
component tree of the corresponding UIViewRoot.
My simplified scenario is like this:
I have two components, both are containing a <a4j:jsfunction>. The JSFunction is responsible for loading my components content dynamically
by using an action listener.
The faclet-page might look like this:
<ui:composition> <h:form> <h:panelGroup id="component1"> <a4j:jsfunction name="getContent1ViaAjax()" actionListener="#{contentManager.getContentForComponent1}" reRender="component1" eventsQueue="queue1" ignoreDuplicateResponses="true" requestDelay="300" /> </h:panelGroup> <h:panelGroup id="component2"> <a4j:jsfunction name="getContent2ViaAjax()" actionListener="#{contentManager.getContentForComponent2}" reRender="component2" eventsQueue="queue2" ignoreDuplicateResponses="true" requestDelay="300"/> </panelGroup> <!-- start the ajax-requests during page load --> <script type="text/javascript">getContent1ViaAjax(); getContent2ViaAjax()</script> </h:form> <ui:composition>
This is what is does:
The page is geeting rendered and directly afterwards, there are two ajax-reuqests performed in parallel because of the java-script call to both functions. So far no problem. The action listeners (...getContentForComponent...) get called
and the content of the components is added programmatically to the components in the INVOKE APPLICATION phase. By using reRender, the result is rendered correctly - both components are filled
rendered filled up with their "dynamic" content.
The problem starts when the dynamic added content is performing some further ajax action.
As an example component1 might look like this after getting filled via the action listener:
... <h:panelGroup id="component1"> <a4j:jsfunction name="getContent1ViaAjax()" actionListener="#{contentManager.getContentForComponent1}" reRender="component1" eventsQueue="queue1" ignoreDuplicateResponses="true" requestDelay="300" /> <!-- programmatically added content (would be look like this in tag-style) --> <!-- input-component bound to "myBean" --> <h:inputText value="#{myBean.inputValue}" /> <!-- save the input value and reload the content of the component by calling the jsfunction again via commandButton-click --> <!-- instead of using a a4j:commandButton I prefer to call my JSFunction to reload the content on the component (should be the same) --> <h:commandButton onClick="getContent1ViaAjax();return;" value="save value" /> </h:panelGroup> ...
After the initial parallel ajax load of the component´s content I´m sometimes (not always) faced to the problem that in the upper example the input-expression #{myBean.inputValue} isn´t bound to "myBean" (which is in session scope).
Then, if the first request after this "initial load" (triggert via the commandButton) happens, an empty inputText is shown again even I entered something in the input-field.
I did some debugging and realized that in this case the model ("myBean") isn´t updated in UPDATE MODEL VALUES phase. I did some further debugging on the view root and found the reason for this:
There seem to be no <h:inputText>-Component in the ViewRoot at all but it was definitly added to component1 by the action listener.
So I look at 3 request now: the 2 parallel requests (initial load of component content) and the third request (first reload of one component´s content):
request1 = load content of compoment1 by calling getContent1ViaAjax()
request2 = load content of compoment1 by calling getContent2ViaAjax()
...
request3 = load content of compoment1 by calling getContent1ViaAjax() again for the first time after the "inital load"
If I dump the view root (via a phase listener in the RENDER_RESPONSE phase) for each of the requests I can see that the view root in request1 contains the input-component.
Request2 shows the view root without the input-component so modifications performed by request1 (if happend before in parallel) seem to be not visible here. This implicates that both requests use a copy of the (same) view root (anyone now how this is generally handled in JSF?).
My conclusion is like this:
Lets say the request2 needs a little bit longer than request1 than the "final" state for this view context seems to saved by request2 and therefor the changes made by request1
seems to be "overwritten". This is why in request3 there is no input-component anymore after RESTORE VIEW based on the last saved state and therefor no value is updated in "myBean". Only now
the input-compoment is added again through the action listener call. After this it seems to be available forever and everthing works.
So the last running request seem to be responsible for saving the state of the view but if concurrent modification happenes by different request on the same view, they get lost.
Now the bad thing is, that I almost evertime have to do an extra reload / request the use my dynamic (via ajax) constructed components in a correct way because only then, everything seems to be present in the view state.
The following questions arises:
1) (How) Is the view state of concurrent requests to the same view context (same viewId and performing a postback) synchronized?
2) If it is not synchonized how can I synchronize it? I tried using the same eventQueue (+requestDelay) for all jsfunctions but it doesn´t seem to work with jsfunction well cause the second of the parallel requests is regared as "similar" and therefor skipped.
Also I want the request to be performed in parallel and not in sequence as it would be then. I think this is one reasing for using ajax at all.
3) Is it a JSF (I´m using MyFaces 1.2.1 / Facelets 1.1.14) problem or an Ajax4JSF-Problem?
Hope someone can help me with this, I´m trying to solve this problem for several days now.
Thanks!