@PostConstruct called multiple time for @ConversationScoped bean
Asked Answered
C

6

5

I have a @ConversationScoped bean, with a start method, like so:

@PostConstruct
public void start() {
    if (conversation.isTransient()) {
        conversation.begin();
        log.debug("conversation.getId(): " + conversation.getId());
    }
}

My problem is that every time the page is refreshed a new conversation is started, a new conversation is also started every time I have an AJAX call to a method in the bean (which is my main problem).

What I really want to happen is for the sam conversation to hang around until I manually call conversation.end(). What am I missing here?

Crowe answered 28/5, 2011 at 12:22 Comment(0)
P
4

Did you checked that the (AJAX) calls include the conversation ID parameter (cid)?

If that's missing, a new conversation is expected to start for each call.

Philander answered 29/5, 2011 at 13:56 Comment(6)
I was making sure my requests had the CID in them, but I missed one request, so it was starting a new conversation. So this fixed half the problem thanks :). I still have the issue of a page refresh starting a new conversation, when I really want it to use the current one. Not sure how/if this is possible.Crowe
You want to reuse the conversation whenever you refresh the page by hitting enter in the address bar of your browser, or do you mean something else by refreshing? In case you indeed mean an actual page refresh by doing a clean non-faces request, then there already is a scope that covers that: it's called the session scope.Philander
I want a conversation scope as I have a shopping cart type requirement. My conversation id is named say, 'shoppingCart'. It all works fine, but, if on the first page the user hits refresh in the browser for whatever reason, it will understandably throw an exception as the cid shoppingCart is already in use. Now, if I don't name the conversation it works, but you would end up with a dead conversation. I can get round this by not naming the conversation, I was just really after a best practice approach. I was hoping there was some way to re-join the shoppingCart conversation or something.Crowe
If you want the conversation to last if the user refreshes the browser, then how would Java ever be able to distinguish two conversations going on in different tabs or windows? Remember that there is no such thing as a cookie per window or tab. Your Java server does not see whether a request comes from a second tab or from a refresh of the current tab. A refresh is the same as a new request, and only cookies can be send along then. If you do find a way to re-join the conversation without providing CID, you have essentially recreated the session scope.Philander
Thanks, that has clarified it for me. I guess that's the downside to using @PostConstruct to start the conversation, you can't really use a named conversation without handling the refresh exception. I guess I could catch the exception and then redirect them to the same page but include the cid=shoppingCart param if I really wanted to, but def a hack.Crowe
Hi @Arjan. How does @ConversationScoped bean behave if there is no cid? Like @RequestScoped bean? (when i checked, RS bean is constructed more than CS bean when a facelet page is loaded and CS bean, more than VS bean)Concession
F
6

Slightly off-topic, but hopefully valuable:

I'm not 100% sure that @PostConstruct is the right place to start a conversation. I'd rather use a faces-event like this:

<f:metadata>
        <f:event type="javax.faces.event.PreRenderViewEvent"
                listener="#{myBean.init}" />
</f:metadata>

and start the conversation if you are sure that you are not in a JSF-postback request.

public void init() {
       if (!FacesContext.getCurrentInstance().isPostback() && conversation.isTransient()) {
          conversation.begin();
       }
    }

If you use Seam 3, it's even easier:

<f:metadata>
   <s:viewAction action="#{myBean.init}" if="#{conversation.transient}" />
</f:metadata>
Fishback answered 30/5, 2011 at 7:24 Comment(2)
I agree, I don't really want a new conversation started if I refresh the page. In fact, if I name the conversation, and I refresh the page I will get an exception as it can't re-use the same conversation id. It would be nice if you could join the conversation like Seam 2. I think I would run into the same problem with your approach, but I do like the idea your approach instead of using the @PostConstruct.Crowe
I just tried it, and it works for the AJAX requests, but if I refresh the page I get 'WELD-000218 Conversation ID myConversation is already in use'. So 50% there :). I know why it is happening on refresh, I just wish I could get it to reuse the current conversation on refresh.Crowe
P
4

Did you checked that the (AJAX) calls include the conversation ID parameter (cid)?

If that's missing, a new conversation is expected to start for each call.

Philander answered 29/5, 2011 at 13:56 Comment(6)
I was making sure my requests had the CID in them, but I missed one request, so it was starting a new conversation. So this fixed half the problem thanks :). I still have the issue of a page refresh starting a new conversation, when I really want it to use the current one. Not sure how/if this is possible.Crowe
You want to reuse the conversation whenever you refresh the page by hitting enter in the address bar of your browser, or do you mean something else by refreshing? In case you indeed mean an actual page refresh by doing a clean non-faces request, then there already is a scope that covers that: it's called the session scope.Philander
I want a conversation scope as I have a shopping cart type requirement. My conversation id is named say, 'shoppingCart'. It all works fine, but, if on the first page the user hits refresh in the browser for whatever reason, it will understandably throw an exception as the cid shoppingCart is already in use. Now, if I don't name the conversation it works, but you would end up with a dead conversation. I can get round this by not naming the conversation, I was just really after a best practice approach. I was hoping there was some way to re-join the shoppingCart conversation or something.Crowe
If you want the conversation to last if the user refreshes the browser, then how would Java ever be able to distinguish two conversations going on in different tabs or windows? Remember that there is no such thing as a cookie per window or tab. Your Java server does not see whether a request comes from a second tab or from a refresh of the current tab. A refresh is the same as a new request, and only cookies can be send along then. If you do find a way to re-join the conversation without providing CID, you have essentially recreated the session scope.Philander
Thanks, that has clarified it for me. I guess that's the downside to using @PostConstruct to start the conversation, you can't really use a named conversation without handling the refresh exception. I guess I could catch the exception and then redirect them to the same page but include the cid=shoppingCart param if I really wanted to, but def a hack.Crowe
Hi @Arjan. How does @ConversationScoped bean behave if there is no cid? Like @RequestScoped bean? (when i checked, RS bean is constructed more than CS bean when a facelet page is loaded and CS bean, more than VS bean)Concession
I
3

The concept of the JSR-299 builtin Conversation is a bit broken. At least for JSF apps. Using the @PreRenderViewEvent would basically drop all the benefits of this approach as it would make every @ConversationScoped bean longRunning.

You might try to use Apache MyFaces CODI @ConversationScoped instead. CODI is a CDI Extension library which is known to work well with Apache OpenWebBeans and also with Weld. It additionally also provides @ViewScoped, @ViewAccessScoped (kind of an auto-conversation) and @WindowScoped contexts.

More at: https://cwiki.apache.org/confluence/display/EXTCDI/Index

Incomprehensive answered 24/6, 2011 at 22:53 Comment(1)
Well, you'd certainly don't want to promote every conversation on every @PreRenderViewEvent - agreed on that part. But with some simple checks (no postback, conversation still transient) you'll basically achieve a application layout (wouldn't call it 'pattern') where you'll start a long running conversation on every inital request to a certain page. (BTW, Seam 3 view-actions promote the same approach.) This is something I use pretty often - and I cannot see anything fundamentally wrong with it - but I'm aware that I'm talking to a member of the CDI EG :-)Fishback
J
2

It's all in the docs:

The conversation scope is active:

during all standard lifecycle phases of any JSF faces or non-faces request.

The conversation context provides access to state associated with a particular conversation. Every JSF request has an associated conversation. This association is managed automatically by the container according to the following rules:

Any JSF request has exactly one associated conversation. The conversation associated with a JSF request is determined at the beginning of the restore view phase and does not change during the request.

Any conversation is in one of two states: transient or long-running.

By default, a conversation is transient A transient conversation may be marked long-running by calling Conversation.begin() A long-running conversation may be marked transient by calling Conversation.end()

All long-running conversations have a string-valued unique identifier, which may be set by the application when the conversation is marked long-running, or generated by the container.

If the conversation associated with the current JSF request is in the transient state at the end of a JSF request, it is destroyed, and the conversation context is also destroyed.

If the conversation associated with the current JSF request is in the long-running state at the end of a JSF request, it is not destroyed. Instead, it may be propagated to other requests according to the following rules:

The long-running conversation context associated with a request that renders a JSF view is automatically propagated to any faces request (JSF form submission) that originates from that rendered page. The long-running conversation context associated with a request that results in a JSF redirect (a redirect resulting from a navigation rule or JSF NavigationHandler) is automatically propagated to the resulting non-faces request, and to any other subsequent request to the same URL. This is accomplished via use of a GET request parameter named cid containing the unique identifier of the conversation. The long-running conversation associated with a request may be propagated to any non-faces request via use of a GET request parameter named cid containing the unique identifier of the conversation. In this case, the application must manage this request parameter.

When no conversation is propagated to a JSF request, the request is associated with a new transient conversation. All long-running conversations are scoped to a particular HTTP servlet session and may not cross session boundaries. In the following cases, a propagated long-running conversation cannot be restored and reassociated with the request:

When the HTTP servlet session is invalidated, all long-running conversation contexts created during the current session are destroyed, after the servlet service() method completes. The container is permitted to arbitrarily destroy any long-running conversation that is associated with no current JSF request, in order to conserve resources.

Author: Gavin King, Pete Muir

Johnson answered 30/5, 2011 at 7:20 Comment(1)
This is very useful thank-you. Like I mentioned in other comments, the problem was that I missed adding CID to a single AJAX call in my page, so that started a new conversation and messed things up.Crowe
A
0

IMHO CDI conversations are broken by design and struberg pointed out a promising alternative. In my App I've the same problem and currently I'm refactoring it to CDI + CODI 1 and it just feels right. @ConversationScoped solves all those problems. While refactoring my App I could solve a lot of nasty cases with @ViewAccessScoped. Thank you struberg for pointing us to it!

Annapurna answered 19/8, 2011 at 17:3 Comment(0)
F
0

Oddly enough, if you add an event listener to your Facelet, even if it calls an empty method, the form action of the generated source will have the 'cid' parameter and therefore, the AJAX call will not create a new conversation. Without the event listener, the 'cid' is missing the in the form action.

<f:metadata>
    <f:event listener="#{myBean.dummy}" type="preRenderView" />
</f:metadata>

MyBean.java

public void dummy() {}
Foursome answered 23/6, 2013 at 3:40 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.