Hi
Ok, now I understand the use case.
The code we have right now in MyFaces aims to propagate
PreDestroyViewMapEvent properly and in that way allow @PreDestroy
annotation to work correctly.
But the "view scope" and the "view state" are two different but related
things. The relation is one view scope can be shared by multiple view
states, so only if every view state associated with the scope is destroyed,
the algorithm can destroy the view scope.
The problem we need to focus is the lifecycle does not have anything about
how to handle view destruction. In few words,
there is:
ViewHandler.restoreView(...)
ViewHandler.saveView(...)
but there is no
ViewHandler.destroyView(...)
Unfortunately there is no easy hack for this. At least you need to update
ViewHandler, ResponseStateManager and optionally StateManagementStrategy.
The first thing I can imagine, based on the code we have in MyFaces is add
this method:
public void ResponseStateManager.destroyState(facesContext, viewId)
This method should take the view state and the viewId and with these two,
the algorithm can calculate the associated token for the view and destroy
the view state. In that way, you can skip the UIViewRoot creation (as I
said before, the key point is most of the times you don't have the
UIViewRoot instance to destroy the scope, but you still need to invoke
PreDestroyViewMapEvent, usually using a dummy one, because the important
thing is to handle @PreDestroy annotations).
The idea is destroyState(...) is called from ViewHandler.destroyView(...)
using the same pattern for ViewHandler.restoreView(...) (if
StateManagementStrategy is set, call
StateManagementStrategy.destroyView(...)).
Then you need a way to invoke it from the lifecycle (restore view phase).
From the client, a POST request (could use ajax or not) should be sent with
javax.faces.ViewState and clientWindow information. An special parameter
could help, but anyway a javascript method could be created that build the
request. Please note what you are really doing from the client side is
closing a window, so the client window api could be affected. In portlet
case, you could affect many views at once, so I'm not sure how to deal that
case.
In practice what we have here is an event (CloseWindowEvent) triggered in
the client that needs to be propagated to the server. There is no API in
JSF for that kind of interaction, that it is not an AJAX operation by
itself, but something else.
I think the minimal thing to do without going too far is to add
ResponseStateManager.destroyState(facesContext). I would suggest to
implement from ViewHandler.
regards,
Leonardo Uribe
2016-07-21 13:33 GMT-05:00 Bauke Scholtz <balusc_at_gmail.com>:
> On the other hand, this could also be considered a bug in the JSF
> implementation itself, that it doesn't destroy the server side view state
> during PreDestroyViewMapEvent.
>
> And while thinking, another possibility to skip the implicit buildView()
> step during restoreView() is to have a context.setBuildingView(false) like
> as there is already a context.setProcessingEvents(true).
>
> Cheers, B
>
> On Thu, Jul 21, 2016, 20:10 Bauke Scholtz <balusc_at_gmail.com> wrote:
>
>> Hi,
>>
>> A real world use case where explicitly destroying server side view state
>> makes sense is below:
>>
>> - Set number of physical views to 3 (in Mojarra, use
>> com.sun.faces.numberOfLogicalViews context param and in MyFaces use
>> org.apache.myfaces.NUMBER_OF_VIEWS_IN_SESSION context param).
>> - Open a JSF page in a tab and keep it open all time.
>> - Open the same page in another tab and then close this tab.
>> - Open the same page in another tab and then close this tab.
>> - Open the same page in another tab and then close this tab.
>> - Submit a JSF form in the first tab.
>>
>> Without explicitly destroying the server side view state, it would fail
>> with ViewExpiredException. If the page references a OmniFaces @ViewScoped,
>> then it will work.
>>
>> Cheers, B
>>
>> On Thu, Jul 21, 2016 at 8:05 PM, Bauke Scholtz <balusc_at_gmail.com> wrote:
>>
>>> Hi,
>>>
>>> 1. The technical reason that I'd like to restore UIViewRoot's state is
>>> being able to properly invoke all listeners associated with
>>> the PreDestroyViewMapEvent. To the point, only restoring the component's
>>> own state (preferably only those listeners) is sufficient for the task of
>>> OmniFaces @ViewScoped, not restoring the whole tree.
>>>
>>> 2. The PreDestroyViewMapEvent already deals with destroying view map and
>>> associated view scoped beans. Only the server side view state remains in
>>> memory. Client side view state is not a problem. The main goal is
>>> immediately destroying view scope map and beans. As to inactive views,
>>> that's exactly why OmniFaces @ViewScoped uses the unload trick for this and
>>> this indeed sends a command through synchronous ajax. The
>>> ViewHandler#restoreView() could be used instead, but this implicitly also
>>> invokes buildView() which is unnecessarily expensive for the purpose of
>>> only firing the PreDestroyViewMapEvent. The alternative would be extracting
>>> buildView() step from the restoreView() method into a separate method.
>>>
>>> Cheers, B
>>>
>>>
>>> On Thu, Jul 21, 2016 at 7:19 PM, Leonardo Uribe <leonardo.uribe_at_irian.at
>>> > wrote:
>>>
>>>> Hi
>>>>
>>>> About 1.
>>>>
>>>> UIViewRoot is an special component that requires some steps to be
>>>> restored, but I'm not sure if that's required outside the state saving
>>>> algorithm.
>>>>
>>>> I think the reason you need to restore the UIViewRoot is to have a
>>>> reference to a view, so you can destroy it later, right? If that so, the
>>>> problem is another different one.
>>>>
>>>> In MyFaces restore the UIViewRoot is a complex task, because UIViewRoot
>>>> not only holds state related to the component itself, but also information
>>>> about the tree structure (FaceletState and component id counters) and
>>>> viewScope bindings. It started with restoreState(...), but now there are a
>>>> lot of lines of code to restore it properly.
>>>>
>>>> About 2.
>>>>
>>>> In MyFaces there a CDI interface to deal with views called
>>>> ViewScopeProvider. There is a method called onSessionDestroyed() and
>>>> another one called destroyViewScopeMap(facesContext,viewScopeId) which
>>>> deals with the problem.
>>>>
>>>> On server side state saving, the algorithm applies some logic to remove
>>>> the stored view states that are not being used, and use the SPI api to
>>>> remove the associated viewScope if necessary.
>>>>
>>>> But please note the big problem is not the current view, but the
>>>> inactive views that needs to be removed and their association with CDI
>>>> (because there is no UIViewRoot instance to grab them). ViewScopeProvider
>>>> SPI interface helps to fill the gap between the view state and the CDI.
>>>>
>>>> This is considered an implementation detail, so we don't really need
>>>> anything from spec perspective. But there is nothing when client side state
>>>> saving is used.
>>>>
>>>> Remember there was a change in the spec where ViewScope is now stored
>>>> in the server only, so when client side state saving is used, there is no
>>>> way to clean the scope, because we don't have the API to do it. So, a
>>>> possible use case I can imagine is, if we have something to detect when a
>>>> window or tab has been closed on the client, we could send a message to the
>>>> server and with the api we could clear the scope.
>>>>
>>>> In conclusion an API to remove the view state or the associated
>>>> viewScope state has sense to me, but I would like to see the possible
>>>> scenarios where this API could be useful. I have only identified one use
>>>> case, but it looks that it requires something to send a command through
>>>> ajax.
>>>>
>>>> regards,
>>>>
>>>> Leonardo Uribe
>>>>
>>>> 2016-07-21 8:29 GMT-05:00 Bauke Scholtz <balusc_at_gmail.com>:
>>>>
>>>>> Hi,
>>>>>
>>>>> During working on "immediately destroy bean instance and server side
>>>>> view state on page unload" feature of OmniFaces @ViewScoped, I stumbled
>>>>> upon two shortcomings in public JSF API.
>>>>>
>>>>>
>>>>> 1. There is no clean way to restore only the state of a specific
>>>>> component. The ResponseStateManager#getState() returns the entire view
>>>>> state while I'd like to restore only the component state of UIViewRoot
>>>>> instance itself. See also
>>>>> https://github.com/omnifaces/omnifaces/blob/da3847ff41f93e2a4f847eb2176c2343a32b968b/src/main/java/org/omnifaces/viewhandler/OmniViewHandler.java#L141
>>>>> In other words, there is no JSF API provided way to obtain exactly the part
>>>>> of the view state as expected by 2nd argument of
>>>>> UIComponent#restoreState().
>>>>>
>>>>> I'm only not exactly sure if adding a public API on that would be a
>>>>> great idea. Perhaps we should leave it, but just throwing here for thoughts.
>>>>>
>>>>>
>>>>> 2. There is no JSF API provided way to physically remove the entire
>>>>> view state from the server side state. See also:
>>>>> https://github.com/omnifaces/omnifaces/blob/296c4d03f47a8d69360f14ef74c5d6e3fe243ff6/src/main/java/org/omnifaces/util/Hacks.java#L362
>>>>>
>>>>>
>>>>> I'd expect to see a ResponseStateManager#destroyViewState() or
>>>>> #removeViewState() taking FacesContext argument which does exactly the task
>>>>> of destroying/removing the server side view state associated with the
>>>>> currently set view root. This could also be called by the implementation
>>>>> directly after PreDestroyViewMapEvent, just for more optimal memory usage,
>>>>> and also a more robust LRU sequence of view states in Mojarra.
>>>>>
>>>>>
>>>>> What do you guys think?
>>>>>
>>>>> Cheers, B
>>>>>
>>>>
>>>>
>>>
>>