jsr372-experts@javaserverfaces-spec-public.java.net

[jsr372-experts] Re: [jsr372-experts mirror] Re: Re: JSF API to extract component state from view state, and to physically remove view state from server side state

From: Leonardo Uribe <leonardo.uribe_at_irian.at>
Date: Fri, 22 Jul 2016 15:29:32 -0500

Hi

Well, in fact I was thinking that destroyState(FacesContext) may trigger
PreDestroyViewMapEvent or not. The reason is the view state and the view
scope (which hold the view map) behaves a bit different. Suppose you are in
page /a.xhtml and there is a non ajax postback to /a.xhtml. In that case
there are two view states (a and a') pointing to the same view scope, and
only when both a and a' are discarded, the associated view scope can be
discarded.

That's why there is in MyFaces these two methods:

onSessionDestroyed(): the session was invalidated by timeout or
intentionally, destroying all view scope managed beans.

destroyViewScopeMap(facesContext,viewScopeId): destroy a view scope
associated with an id.

Both methods are implementation details. But the idea I have been thinking
about is destroyState(FacesContext) could activate destroyViewScopeMap(...)
internally and that method perform PreDestroyViewMapEvent. By previous JSF
2.x specs, PreDestroyViewMapEvent is triggered by the viewMap
implementation internally.

If the name is not very intuitive, we could find another different name
like ResponseStateManager#processDestroyState(...), the important thing is
let the activation of PreDestroyViewMapEvent as an implementation detail.

regards,

Leonardo Uribe



2016-07-22 5:21 GMT-05:00 Bauke Scholtz <balusc_at_gmail.com>:

> Hi,
>
> Thank you for your thoughts Leonardo. I understand that a single view
> scope can cover multiple view states (Portlets). It should be possible to
> track all those view states by some key which is in turn stored in view
> map. OmniFaces is by the way not targeted at Portlets, it has a required
> Servlet API dependency. I only indeed didn't look closer at new client
> window thing yet. Will do later.
>
> ResponseStateManager#destroyState(FacesContext) is indeed a good starting
> point. We could specify that it must be called by end of
> PreDestroyViewMapEvent. The implementation could with client side state
> saving just perform a NOOP and with server side state saving physically
> remove the state(s) from the session.
>
> Cheers, B
>
>
> On Fri, Jul 22, 2016 at 2:19 AM, Leonardo Uribe <leonardo.uribe_at_irian.at>
> wrote:
>
>> 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
>>>>>>>
>>>>>>
>>>>>>
>>>>>
>>>>
>>
>