[
https://jira.jboss.org/jira/browse/JBSEAM-3368?page=com.atlassian.jira.pl...
]
Arron Ferguson commented on JBSEAM-3368:
----------------------------------------
There is a lot of noise in the provided code, specifically:
I've taken out as much noise is as possible.
1. The pages.xml contains navigation rules based on
#{identity.loggedIn},
yet there is no way to authenticate.
Remove all three cases of this showing up, yes, thanks.
2. The view extensions are inconsistent - some are .html and some are
.xhtml. Perhaps
uploading the test case files as a zipped attachment would provide a more accurate
test case? (without the jar files)
A typo. I was new to JIRA and didn't realize you can do that. Yes, I have uploaded a
gzip attached (minus the JAR files of course).
3. The use case seems excessively complex just to reproduce a single
issue - are
register2, register3, etc really needed?
No, you don't need them. If you run the example without them you still see the same
behavior. I've left them in there because together all of these pages offer the
following with regards to natural conversations:
- beginning a conversation
- ending a conversation
- joining a conversation
- going forwards in a conversation (navigation wise)
- going backwards in a conversation (navigation wise)
- canceling (ending) a conversation
- restricting access to a page based on the requirement of needing a natural conversation
Specifically:
- index.xhtml clears any existing conversations
- register.xhtml starts a natural conversation
- register1.xhtml offers two choices: cancel existing natural conversation and continue
(forward) or join existing conversation
- register2.xhtml offers only one choice: continue or join existing conversation
- register3.xhtml offers two choices: continue (forward) with existing conversation and go
back one page in the existing conversation
- register4.xhtml ends a natural conversation
This example is useful because it demonstrates all of the possible interactions (at least
that I could think of) that one can have with natural conversations which could be useful
for further debugging and trouble shooting. But as I mentioned above, feel free to remove
register pages 2 - 4 from the example and simply type the URL register1.xhtml and you will
see the exact same behavior.
4. Conversation propagation seems to be declared both in pages.xml
and in the view,
for example the following navigation rule in the <page> entry for register.xhtml:
...
You need to use one or the other, not both.
Thanks, I didn't know that. I've removed it from the uploaded gzip.
conversation id evaluated to null (natural conversations)
---------------------------------------------------------
Key: JBSEAM-3368
URL:
https://jira.jboss.org/jira/browse/JBSEAM-3368
Project: Seam
Issue Type: Bug
Components: Core
Affects Versions: 2.0.3.CR1
Environment: Linux Ubuntu 7.10, Intel Pentium 4, 3GHz, 1 GB RAM, Tomcat 6.0.16
Reporter: Arron Ferguson
Assignee: Shane Bryzak
Priority: Critical
Fix For: 2.1.0.GA
Attachments: conversation-src.tar.gz
If the user attempts to go to a page that is restricted based on what is in the pages.xml
file, two things go wrong: 1) an exception is thrown (java.lang.IllegalStateException:
conversation id evaluated to null: [name of natural conversation]), 2) the user can still
view the page that is supposed to be restricted (i.e., using the restrict element in the
pages.xml file).
Setup Instructions:
1.) Set up Tomcat 6.x
2.) Create web app project structure
3.) Use the following for your pages.xml file (copy/paste):
<?xml version="1.0" encoding="UTF-8"?>
<pages
xmlns="http://jboss.com/products/seam/pages"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://jboss.com/products/seam/pages
http://jboss.com/products/seam/pages-2.0.xsd"
http-port="8080" https-port="8443"
no-conversation-view-id="/index.xhtml"
login-view-id="/index.xhtml">
<conversation name="regmem"
parameter-name="memberID"
parameter-value="#{memberreg.tempKey}"/>
<page view-id="*" scheme="http" />
<page view-id="/register.xhtml">
<description>Register Member: #{memberreg}</description>
<navigation from-action="#{memberreg.start}">
<begin-conversation join="true" conversation="regmem"/>
<redirect view-id="/register1.xhtml"/>
</navigation>
<navigation>
<rule if="#{identity.loggedIn}">
<redirect view-id="/memberadmin.xhtml"/>
</rule>
<rule if="#{memberreg.acceptedInformation}">
<redirect view-id="/register3.xhtml"/>
</rule>
<rule if="#{memberreg.agree}">
<redirect view-id="/register2.xhtml"/>
</rule>
<rule if="#{memberreg.started}">
<redirect view-id="/register1.xhtml"/>
</rule>
</navigation>
</page>
<page view-id="/register1.xhtml" conversation-required="true"
conversation="regmem">
<description>Register Member: #{memberreg}</description>
<restrict>#{memberreg.started}</restrict>
<navigation from-action="#{memberreg.agreeToLicense}">
<redirect view-id="/register2.xhtml"/>
</navigation>
<navigation from-action="#{memberreg.cancel}">
<end-conversation/>
<redirect view-id="/index.xhtml"/>
</navigation>
<navigation>
<rule if="#{identity.loggedIn}">
<redirect view-id="/memberadmin.xhtml"/>
</rule>
<rule if="#{memberreg.acceptedInformation}">
<redirect view-id="/register3.xhtml"/>
</rule>
<rule if="#{memberreg.agree}">
<redirect view-id="/register2.xhtml"/>
</rule>
</navigation>
</page>
<page view-id="/register2.xhtml" conversation-required="true"
conversation="regmem">
<description>Register Member: #{memberreg}</description>
<restrict>#{memberreg.agree}</restrict>
<navigation from-action="#{memberreg.next}">
<rule if="#{memberreg.readyToConfirm}">
<redirect view-id="/register3.xhtml"/>
</rule>
<rule if="#{not memberreg.readyToConfirm}">
<redirect view-id="/register2.xhtml"/>
</rule>
</navigation>
<navigation>
<rule if="#{identity.loggedIn}">
<redirect view-id="/memberadmin.xhtml"/>
</rule>
<rule if="#{memberreg.acceptedInformation}">
<redirect view-id="/register3.xhtml"/>
</rule>
</navigation>
</page>
<page view-id="/register3.xhtml" conversation-required="true"
conversation="regmem">
<description>Register Member: #{memberreg}</description>
<restrict>#{memberreg.readyToConfirm}</restrict>
<navigation from-action="#{memberreg.acceptInformation}">
<redirect view-id="/register4.xhtml"/>
</navigation>
<navigation from-action="#{memberreg.editInformation}">
<redirect view-id="/register2.xhtml"/>
</navigation>
<navigation>
<rule if="#{identity.loggedIn}">
<redirect view-id="/memberadmin.xhtml"/>
</rule>
</navigation>
</page>
<page view-id="/register4.xhtml" conversation-required="true"
conversation="regmem">
<description>Register Member: #{memberreg}</description>
<restrict>#{memberreg.acceptedInformation}</restrict>
<navigation from-action="#{memberreg.end}">
<end-conversation before-redirect="false"/>
<redirect view-id="/index.xhtml"/>
</navigation>
</page>
<!-- START EXCEPTIONS -->
<exception class="org.jboss.seam.security.NotLoggedInException"
log="false">
<redirect view-id="/index.xhtml">
<message severity="warn">Your session ended or
expired.</message>
</redirect>
</exception>
<exception class="org.jboss.seam.security.AuthorizationException"
log="false">
<end-conversation/>
<redirect view-id="/index.xhtml">
<message severity="warn">You are not authorized.</message>
</redirect>
</exception>
<!-- END EXCEPTIONS -->
</pages>
4.) Create two Java classes (registration bean and POJO):
Registration Bean:
package com.test;
import javax.ejb.Remove;
import javax.persistence.Column;
import javax.persistence.NoResultException;
import java.io.Serializable;
import java.util.Date;
import javax.persistence.EntityManager;
import org.jboss.seam.annotations.In;
import org.jboss.seam.annotations.End;
import org.jboss.seam.annotations.Destroy;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.annotations.Out;
import org.jboss.seam.annotations.Scope;
import org.jboss.seam.faces.FacesMessages;
import org.jboss.seam.faces.Renderer;
import static org.jboss.seam.ScopeType.CONVERSATION;
@Scope(CONVERSATION)
@Name("memberreg")
public class MemberRegistrationAction implements Serializable
{
private static long tempKey = 0;
@Out(required = false) @In(required = false)
private Member newMember;
@In(create = true)
private Renderer renderer;
private boolean started = false;
private boolean agree = false;
private boolean acceptedInformation = false;
private boolean readyToConfirm = false;
private boolean stillNeedsToEdit = false;
private String confirm = "";
public String getConfirm() { return confirm; }
public void setConfirm(String confirm) { this.confirm = confirm; }
public long getTempKey() { return tempKey; }
public void setTempKey(long tempKey) { this.tempKey = tempKey; }
public Member getNewMember() { return newMember; }
/////////////////////////////////////////////
// CONVERSATION METHODS
/////////////////////////////////////////////
public void start()
{
newMember = new Member();
if(tempKey >= Long.MAX_VALUE)
{
tempKey = 0;
}
tempKey++;
started = true;
}
public void agreeToLicense() { agree = true; }
public void cancel()
{
started = false;
agree = false;
acceptedInformation = false;
readyToConfirm = false;
stillNeedsToEdit = false;
}
public void next() { readyToConfirm = this.verify(); }
private boolean verify() {
return true; // test purposes, always true
}
public void acceptInformation()
{
acceptedInformation = true;
stillNeedsToEdit = false;
this.complete();
}
public void editInformation()
{
stillNeedsToEdit = true;
acceptedInformation = false;
}
private void complete() {
; // write to JPA/Hibernate
}
public void end() {
; // something, something, something, dark side
}
/////////////////////////////////////////////
public boolean isStarted() { return started; }
public boolean isAgree() { return agree; }
public boolean isAcceptedInformation() { return acceptedInformation; }
public boolean isReadyToConfirm() { return readyToConfirm; }
public boolean isStillNeedsToEdit() { return stillNeedsToEdit; }
public void destroy()
{
started = false;
agree = false;
acceptedInformation = false;
readyToConfirm = false;
stillNeedsToEdit = false;
}
}
POJO:
package com.test;
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;
import org.hibernate.validator.Length;
import org.hibernate.validator.NotEmpty;
import org.hibernate.validator.NotNull;
import org.jboss.seam.annotations.Name;
@Entity @Name("member") @Table(name="member")
public class Member implements Serializable {
@Id
@GeneratedValue
@Column(name = "id")
private long id = -1;
@NotNull
@Length(min=2, max=20, message="Must be between 2 and 20 chars long")
@Column(name = "lastname")
private String lastName;
@NotNull
@Length(min=2, max=20, message="Must be between 2 and 20 chars long")
@Column(name = "firstname")
private String firstName;
public long getId() { return id; }
public void setId(long id) { this.id = id; }
public String getLastName() { return lastName; }
public void setLastName(String lastName) { this.lastName = lastName; }
public String getFirstName() { return firstName; }
public void setFirstName(String firstName) { this.firstName = firstName; }
@Override
public String toString() { return firstName + " " + lastName; }
}
5.) Create XHTML files (template, index, register1 - register4):
///////////////////////////////////
template.xhtml:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html
xmlns="http://www.w3.org/1999/xhtml" lang="en"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core" >
<head>
<title>something.com</title>
</head>
<body>
<div>
<h:messages globalOnly="true"/><br/><br/>
<ui:insert name="maincontent">
</ui:insert>
</div>
</body>
</html>
///////////////////////////////////
index.html:
<html
xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:s="http://jboss.com/products/seam/taglib">
<!-- <ui:composition template="./template.xhtml"> -->
<ui:composition template="./template.xhtml">
<ui:define name="maincontent">
<s:link view="/register.html" value="click here to register"
propagation="none"/>
</ui:define>
</ui:composition>
</html>
///////////////////////////////////
register.html
<html
xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:s="http://jboss.com/products/seam/taglib">
<ui:composition template="./template.xhtml">
<ui:define name="maincontent">
<p>Click to start natural conversation:</p>
<h:form id="testform">
<h:outputText value="Click: "/>
<h:commandButton value="Register"
action="#{memberreg.start}">
<s:conversationName value="regmem"/>
<s:conversationPropagation type="join"/>
</h:commandButton>
</h:form>
</ui:define>
</ui:composition>
</html>
///////////////////////////////////
register1.xhtml
<html
xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:s="http://jboss.com/products/seam/taglib" >
<ui:composition template="./template.xhtml">
<ui:define name="maincontent">
<h:form id="agreeform">
<h:commandButton id="agreebut1" value="Agree"
action="#{memberreg.agreeToLicense}">
<s:conversationName value="regmem"/>
<s:conversationPropagation type="join"/>
</h:commandButton>
<br/><p>Click cancel to kill the natural conversation
early</p>
<h:commandButton id="disagreebut1" value="Disagree"
action="#{memberreg.cancel}">
<s:conversationName value="regmem"/>
<s:conversationPropagation type="end"/>
</h:commandButton>
</h:form>
</ui:define>
</ui:composition>
</html>
///////////////////////////////////
register2.xhtml
<html
xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:s="http://jboss.com/products/seam/taglib" >
<ui:composition template="./template.xhtml">
<ui:define name="maincontent">
<h:form id="formfields">
<p>Content inserted for testing purposes when this button
clicked:</p>
<h:commandButton id="next" value="Next"
action="#{memberreg.next}">
<s:conversationName value="regmem"/>
<s:conversationPropagation type="join"/>
</h:commandButton>
</h:form>
</ui:define>
</ui:composition>
</html>
///////////////////////////////////
register3.xhtml
<html
xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:rich="http://richfaces.org/rich"
xmlns:s="http://jboss.com/products/seam/taglib" >
<ui:composition template="./template.xhtml">
<ui:define name="maincontent">
<h:form id="formfields" rendered="#{not
identity.loggedIn}">
<h:commandButton id="editinfo" value="Edit Information"
action="#{memberreg.editInformation}">
<s:conversationName value="regmem"/>
<s:conversationPropagation type="join"/>
</h:commandButton>
     
<h:commandButton id="confirmreg" value="Confirm
Registration"
action="#{memberreg.acceptInformation}">
<s:conversationName value="regmem"/>
<s:conversationPropagation type="join"/>
</h:commandButton>
</h:form>
</ui:define>
</ui:composition>
</html>
///////////////////////////////////
register4.xhtml
<html
xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:s="http://jboss.com/products/seam/taglib" >
<ui:composition template="./template.xhtml">
<ui:define name="maincontent">
<p>Click back to finish natural conversation.</p>
<s:link value="Back to home." action="#{memberreg.end}"
propagation="end"/>
</ui:define>
</ui:composition>
</html>
6.) Use the following JARs for your web app (for running inside of Tomcat 6):
activation.jar
antlr.jar
asm.jar
cglib.jar
commons-beanutils.jar
commons-codec-1.3.jar
commons-collections.jar
commons-digester.jar
commons-lang.jar
commons-logging.jar
DA.jar
dom4j.jar
ejb-api.jar
filelist.txt
hibernate-annotations.jar
hibernate-commons-annotations.jar
hibernate-entitymanager.jar
hibernate.jar
hibernate-search.jar
hibernate-validator.jar
javassist.jar
jboss-archive-browsing.jar
jboss-el.jar
jboss-seam-debug.jar
jboss-seam-ioc.jar
jboss-seam.jar
jboss-seam-mail.jar
jboss-seam-ui.jar
jsf-api.jar
jsf-facelets.jar
jsf-impl.jar
jstl.jar
jta.jar
lucene-core.jar
mail.jar
persistence-api.jar
richfaces-api.jar
richfaces-impl.jar
richfaces-ui.jar
spring.jar
urlrewrite-3.1.0.jar
7.) (Optional) Set up facelets but you may also want to make the default extension html:
<servlet-mapping>
<servlet-name>Faces Servlet</servlet-name>
<url-pattern>*.html</url-pattern>
</servlet-mapping>
Steps to reproduce bug:
1.) Start web app inside of Tomcat 6
2.) using your browser's URL text field, type in the name of a register1.html,
register2.html, or register3.html. At this point your web app will generate an exception
stack which will be caused by:
java.lang.IllegalStateException: conversation id evaluated to null:
The conversation is evaluated to null. Since conversations are a layer on top of
sessions, the same behavior is expected which is a faces message purporting of no
conversation existing and a redirect to the URL specified in the pages.xml attribute
no-conversation-view-id="/index.xhtml" (so in this case index.xhtml page). It
should not allow the user to see the page and generate exceptions.
--
This message is automatically generated by JIRA.
-
If you think it was sent incorrectly contact one of the administrators:
https://jira.jboss.org/jira/secure/Administrators.jspa
-
For more information on JIRA, see:
http://www.atlassian.com/software/jira