简体   繁体   中英

No long-running conversations - IllegalArgumentException: Stack must not be null

I have a very simple application with just 2 pages on WebLogic 10.3.2 (11g), Seam 2.2.0.GA. I have a command button in each, which makes a redirect-after-post to the other. This works well, as I see the URL of the current page I am seeing in the address bar.

BUT , even though I have no long-running conversations defined, after a random number of clicks, and - I think - after a random number of seconds (~10s - 60s) I get the lovely exception at the end of this post.

Now, if I have understood how temporary conversations work when redirecting this happens:

  1. When I first see my application, the url is http://localhost:7001/myapp
  2. When I click the button in pageA.xhtml, I end up in "pageB.xhtml?cid=26". This is normal because Seam extends the temporary conversation of the first request to last until the renderResponse phase of the redirect. So, it uses the cid (Conversation Id) of the extended temporary conversation to find any propagated parameters.

  3. When I click the button in pageB.xhtml, I end up in pageA.xhtml?cid=26

The same cid was given to the new extended temporary conversation. This is normal because the conversation ended at the end of the previous redirect-after-post, and not the number 26 is free to use as a cid.

Is this all correct? If yes, why does this happen: If I re-type the applications home address (showing pageA) and re-click, I end up in pageB.xhtml?cid=29, which is a different number than 26. But 26 has ended after the previous RenderResponse phase, befire I re-types the url. Why is it not used instead of 29?

So, to sup up, 2 questions:

  1. Why do I get the exception, even though I have not started any long-running conversations?
  2. What happens exactly with the cid? On what basis does it change?

Cheers,

UPDATE:

Additional information: I use h:commandButtons like this in page A:

<h:commandButton action="showPageB" value="Show page B" />

and in page B

<h:commandButton action="showPageA" value="Show page A" />

navigation pageA.page.xml:

<page view-id="/pageA.xhtml">
<navigation>
    <rule if-outcome="showPageB">
        <redirect view-id="/pageB.xhtml" />
    </rule>
</navigation>
</page>

and a very similar for pageB.

As for the conversation timeout, I have set it to 1h. Note that it is irrelevant, because as I read here , it is only meant for background conversations. The stacktrace follows:

Error 500--Internal Server Error

    java.lang.IllegalArgumentException: Stack must not be null
    at org.jboss.seam.core.ConversationEntry.(ConversationEntry.java:45)
    at org.jboss.seam.core.ConversationEntries.createConversationEntry(ConversationEntries.java:53)
    at org.jboss.seam.core.Manager.createConversationEntry(Manager.java:664)
    at org.jboss.seam.core.Manager.beforeRedirect(Manager.java:836)
    at org.jboss.seam.faces.FacesManager.beforeRedirect(FacesManager.java:66)
    at org.jboss.seam.faces.FacesManager.redirect(FacesManager.java:182)
    at org.jboss.seam.faces.Navigator.redirect(Navigator.java:55)
    at org.jboss.seam.navigation.RedirectNavigationHandler.navigate(RedirectNavigationHandler.java:61)
    at org.jboss.seam.navigation.Rule.execute(Rule.java:101)
    at org.jboss.seam.navigation.Navigation.navigate(Navigation.java:58)
    at org.jboss.seam.navigation.Pages.navigate(Pages.java:203)
    at org.jboss.seam.jsf.SeamNavigationHandler.handleNavigation(SeamNavigationHandler.java:42)
    at com.sun.faces.application.ActionListenerImpl.processAction(ActionListenerImpl.java:130)
    at javax.faces.component.UICommand.broadcast(UICommand.java:387)
    at org.ajax4jsf.component.AjaxViewRoot.processEvents(AjaxViewRoot.java:324)
    at org.ajax4jsf.component.AjaxViewRoot.broadcastEvents(AjaxViewRoot.java:299)
    at org.ajax4jsf.component.AjaxViewRoot.processPhase(AjaxViewRoot.java:256)
    at org.ajax4jsf.component.AjaxViewRoot.processApplication(AjaxViewRoot.java:469)
    at com.sun.faces.lifecycle.InvokeApplicationPhase.execute(InvokeApplicationPhase.java:82)
    at com.sun.faces.lifecycle.Phase.doPhase(Phase.java:100)
    at com.sun.faces.lifecycle.LifecycleImpl.execute(LifecycleImpl.java:118)
    at javax.faces.webapp.FacesServlet.service(FacesServlet.java:265)
    at weblogic.servlet.internal.StubSecurityHelper$ServletServiceAction.run(StubSecurityHelper.java:227)
    at weblogic.servlet.internal.StubSecurityHelper.invokeServlet(StubSecurityHelper.java:125)
    at weblogic.servlet.internal.ServletStubImpl.execute(ServletStubImpl.java:292)
    at weblogic.servlet.internal.TailFilter.doFilter(TailFilter.java:26)
    at weblogic.servlet.internal.FilterChainImpl.doFilter(FilterChainImpl.java:56)
    at org.ajax4jsf.webapp.BaseFilter.doFilter(BaseFilter.java:530)
    at weblogic.servlet.internal.FilterChainImpl.doFilter(FilterChainImpl.java:56)
    at org.jboss.seam.servlet.SeamFilter$FilterChainImpl.doFilter(SeamFilter.java:83)
    at org.jboss.seam.web.IdentityFilter.doFilter(IdentityFilter.java:40)
    at org.jboss.seam.servlet.SeamFilter$FilterChainImpl.doFilter(SeamFilter.java:69)
    at org.jboss.seam.web.MultipartFilter.doFilter(MultipartFilter.java:90)
    at org.jboss.seam.servlet.SeamFilter$FilterChainImpl.doFilter(SeamFilter.java:69)
    at org.jboss.seam.web.ExceptionFilter.doFilter(ExceptionFilter.java:64)
    at org.jboss.seam.servlet.SeamFilter$FilterChainImpl.doFilter(SeamFilter.java:69)
    at org.jboss.seam.web.RedirectFilter.doFilter(RedirectFilter.java:45)
    at org.jboss.seam.servlet.SeamFilter$FilterChainImpl.doFilter(SeamFilter.java:69)
    at org.ajax4jsf.webapp.BaseXMLFilter.doXmlFilter(BaseXMLFilter.java:178)
    at org.ajax4jsf.webapp.BaseFilter.handleRequest(BaseFilter.java:290)
    at org.ajax4jsf.webapp.BaseFilter.processUploadsAndHandleRequest(BaseFilter.java:388)
    at org.ajax4jsf.webapp.BaseFilter.doFilter(BaseFilter.java:515)
    at org.jboss.seam.web.Ajax4jsfFilter.doFilter(Ajax4jsfFilter.java:56)
    at org.jboss.seam.servlet.SeamFilter$FilterChainImpl.doFilter(SeamFilter.java:69)
    at org.jboss.seam.web.LoggingFilter.doFilter(LoggingFilter.java:60)
    at org.jboss.seam.servlet.SeamFilter$FilterChainImpl.doFilter(SeamFilter.java:69)
    at org.jboss.seam.web.HotDeployFilter.doFilter(HotDeployFilter.java:53)
    at org.jboss.seam.servlet.SeamFilter$FilterChainImpl.doFilter(SeamFilter.java:69)
    at org.jboss.seam.servlet.SeamFilter.doFilter(SeamFilter.java:158)
    at weblogic.servlet.internal.FilterChainImpl.doFilter(FilterChainImpl.java:56)
    at weblogic.servlet.internal.RequestEventsFilter.doFilter(RequestEventsFilter.java:27)
    at weblogic.servlet.internal.FilterChainImpl.doFilter(FilterChainImpl.java:56)
    at weblogic.servlet.internal.WebAppServletContext$ServletInvocationAction.run(WebAppServletContext.java:3592)
    at weblogic.security.acl.internal.AuthenticatedSubject.doAs(AuthenticatedSubject.java:321)
    at weblogic.security.service.SecurityManager.runAs(SecurityManager.java:121)
    at weblogic.servlet.internal.WebAppServletContext.securedExecute(WebAppServletContext.java:2202)
    at weblogic.servlet.internal.WebAppServletContext.execute(WebAppServletContext.java:2108)
    at weblogic.servlet.internal.ServletRequestImpl.run(ServletRequestImpl.java:1432)
    at weblogic.work.ExecuteThread.execute(ExecuteThread.java:201)
    at weblogic.work.ExecuteThread.run(ExecuteThread.java:173)

First of all, it is always helpful to see the relevant code and stacktrace when trying to debug a problem.

Thus I cannot give an answer to your first question. However, I will try to explain how the conversation model works.

This is from the Seam in Action book:

@ScopeType.EVENT = Goes from Restore View to Render Response, but not redirect @ScopeType.CONVERSATION = Goes from Restore VIew to Render Response, and redirect. If long-running conversation, then it spans multiple JSF life cycles.

So imagine you are in a.xhtml you push a button that will take ComponentA and fill some data in it. This component you want to inject and use in b.xhtml ie:

Push commandbutton in a.xhtml performing post, putting some data in ComponentA Now you redirect to next page ( b.xhtml )which uses the ComponentA

@Name("componentB")
@Scope(ScopeType.CONVERSATION)
public class ComponentB {
    @In(create=true)
    ComponentA componentA; //OK
}

So if you now push another button from b.xhtml expecting to be able to inject ComponentA again, that will fail. ie:

@Name("componentC")
@Scope(ScopeType.CONVERSATION)
public class ComponentC {
    @In(create=true)
    ComponentA componentA; //Injection of the component you really want fails (you will get default component)
}

So in the background now, seam has created a new cid for you, ending the previous cid because a conversation scoped component can only live one request.

After seeing your StackTrace and your Use case (after a random number of clicks)

Let's see FacesManages.beforeRedirect (as shown by your StackTrace) documentation

Temporarily promote a temporary conversation to a long running conversation for the duration of a browser redirect

Now, let's see some piece of code of beforeRedirect method

if (isDifferentConversationId(currentPage, targetPage))
    updateCurrentConversationId(targetPage.getConversationId());

...

updateCurrentConversationId is responsible for creating the Stack which must be not null See again your StackTrace

public void updateCurrentConversationId(String id) {
    if (id != null && id.equals(currentConversationId)) {
     // The conversation id has not changed, do nothing       
     return;
  }

After code shown above, your Stack will be created. So i suppose The conversation id has not changed because of The duration of a browser redirect (caused by a random number of clicks) or even a Seam bug when dealing with a navigation with redirect from one page to another and vice-versa

Try the following one for each Page rule (see timeout="0")

<page view-id="/pageA.xhtml" timeout="0">
    <navigation>
        <rule if-outcome="showPageB">
            <redirect view-id="/pageB.xhtml" />
        </rule>
    </navigation>
</page>

I expect now it works fine! But, if not, now, you know why you get your exception

UPDATE

Try <end-conversation/> as a workaround (for each page)

<page view-id="/pageA.xhtml">
    <navigation>
        <rule if-outcome="showPageB">
            <end-conversation/>
            <redirect view-id="/pageB.xhtml" />
        </rule>
    </navigation>
</page>

or (see before-redirect)

<page view-id="/pageA.xhtml">
    <navigation>
        <rule if-outcome="showPageB">
            <end-conversation before-redirect="true"/>
            <redirect view-id="/pageB.xhtml" />
        </rule>
    </navigation>
</page>

Now i hope it works fine!

EDIT

As said by beforeRedirect method

Temporarily promote a temporary conversation to a long running conversation for the duration of a browser redirect. After the redirect, the conversation will be demoted back to a temporary conversation.

It explains why #{conversation.longRunning} outputs true when you go to pageB. Your "long running conversation" caused by your redirect should be destroyed after the Render Response phase.

When using a redirect, Seam appends conversation id paratemer to URL.

Seam in Action book says

At the beginning of the Seam life cycle, Seam looks for the conversation id in a URL parameter

But because when you are back to pageA, you see again the same conversation id parameter , I suppose Seam just create a new one when the url does not contain anyone . And because each long running conversation has it own Timeout period, your long running conversation is keeped alive.

To verify whether what i said is true, do as follows

  • reduce global Timeout period to five seconds (5000 milliseconds)

...

<core:manager conversation-timeout="5000"/>

For each Page, see what #{conversation.timeout} outputs. I expect to see something like Either 5 seconds or 5000 miliseconds. Wait for more than 5 seconds (about 10s) and press button to redirect again. And see whether conversation id parameter has been changed.

You should have provided that information long ago. Now it is much more clear what the problem is.

First of all you shouldn't use a commandButton with an empty action like that. When you in pages.xml write the following:

<page view-id="/pageA.xhtml">
<navigation>
    <rule if-outcome="showPageB">
        <redirect view-id="/pageB.xhtml" />
    </rule>
</navigation>
</page>

It means normally that you have some action that returns showPageB like this:

public String someAction() {
    //Do something complex
    return "showPageB";
}

Anyhow, back to your problem. Do your self a favor and create a Seam component.

@Name("myComponent")
public Class MyComponent {

public String showPageB() {
    return "showPageB";
}

public String showPageA() {
    return "showPageA";
}

}

Change your pages.xml to this:

<page view-id="/pageA.xhtml">
<navigation from action="#{myComponent.showPageB}">
    <redirect view-id="/pageB.xhtml" />
</navigation>

<navigation from action="#{myComponent.showPageA}">
    <redirect view-id="/pageA.xhtml" />
</navigation>

<!-- OR you can do like this -->
<navigation from action="#{myComponent.showPageB}">
    <rule if-outcome="showPageA">
        <redirect view-id="/pageA.xhtml" />
    </rule>
    <rule if-outcome="showPageB">
        <redirect view-id="/pageA.xhtml" />
    </rule>
</navigation>
</page>

Then change xhtml h:commandButton to

<h:commandButton action="#{myComponent.showPageA}" value="showA"/>
<h:commandButton action="#{myComponent.showPageB}" value="showB"/>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM