views:

311

answers:

3

Hi all,

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)
+1  A: 

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.

Shervin
@Shervin Interesting, Shervin (+1) Why does Injection of the ComponentA fails when injecting in ComponentC (I suppose it is because of create=true) Am i right ?
Arthur Ronald F D Garcia
@Shervin None of the pages has a component. I only have two minimal pages with h:commandButtons and the corresponding navigation xml files. Note that I do not begin or end any conversations in the xml files either.
Markos Fragkakis
@Arthur Ronald F D Garcia: No that is not why it fails. Actually the create=true (if there is no @AutoCreate on the component) makes it `not` fail. When you have create=true, it will create the component if it is not in the scope. The reason why I say fail, is because the component you really want (the injected with the correct fields) is not available, because it has already been garbage collected. You have to promote to long-running conversation if you want to be able to use the component in the "second" redirect.
Shervin
@Markos Fragkakis How can you use `h:commandButton` without a component? Please show the xhtml files also, not only stacktrace.
Shervin
If you have no `@AutoCreate` and no `create=true` and only `@In` you will either get a nullpointerexception, or a "RequiredException" stating you need create=true or to give scope explicit.
Shervin
@Shervin please see update.
Markos Fragkakis
@Shervin You can avoid create attribute by using a Expression Language (lookup with option to create). Something like @In("#{componentA}"), Seam creates one if it does not exist.
Arthur Ronald F D Garcia
@Arthur: Yes I know that. EL does auto create, but I wouldn't recommend injecting components through EL. although its quite handy to perform restrict checks in EL: ie `@In("#{s:hasRole('admin')}") boolean isAdmin;`
Shervin
+1  A: 

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.

Arthur Ronald F D Garcia
@Arthur thanks for the answer (+1). Unfortunately, I still get the exception. I read in Seam in Action that the page-defined timeout is used to override the one defined in <core:init>, which specifies when *abandoned* conversations end. Even if it did affect my conversation, the redirect-after-post only lasts some milliseconds, which is much less that 1h.
Markos Fragkakis
@Markos Fragkakis See https://jira.jboss.org/jira/browse/JBSEAM-3901 and http://www.seamframework.org/Community/SeamAddsUnwantedConversationIdToURL
Arthur Ronald F D Garcia
@Arthur Yes, I saw that yesterday. In that issue they have disabled the redirect filter, whereas I have it by default on.
Markos Fragkakis
A: 

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"/>
Shervin
the action attribute *can* be a String: http://www.horstmann.com/corejsf/jsf-tags.html#Table4_15. Why have the overhead of creating a Bean to make a navigation? Let;s say this is my home page. If i press the "Help" button I always want to navigate to the Help page, no bean or method is needed to determine the action.
Markos Fragkakis
Nevertheless, I did what you suggest, and does not solve my problem... Thanks anyway!
Markos Fragkakis
I can't believe it doesn't work. That is simply not possible. That means the error is somewhere else in your seam setup.But why would you want to make a post for a simple navigation? What I use is `<s:link viewId="pageA.xhtml" value="Go to A"/>`You should also read the seam documentation found in http://seamframework.org/Documentation
Shervin