Background
-----------------
https://javaserverfaces.dev.java.net/issues/show_bug.cgi?id=12
Additional Background:
The current client side state saving processing is broken in some cases
(primarily when "verbatim" components are used). A cause of this is
primarily
a "misallignment" when processing state save/restore. Consider the
following example:
JSP
---
<f:view>
<h:form id="form">
<h:panelGrid id="panel" columns="2">
<f:facet name="header" >
<h:outputText id="outputheader" value="this is the header" />
</f:facet>
<h:commandLink id="commandLink">
<h:outputText id="redisplay" value="redisplay"/>
</h:commandLink>
<f:verbatim>
verbatim text
</f:verbatim>
</h:panelGrid>
</h:form>
</f:view>
</html>
Let's focus on the verbatim tag and the components surrounding the
verbatim tag. At the completion of save state processing, we end up
with the following:
In state saving terms, the panel component ends up with the following
structure:
The "Panel" Component has 3 children:
childState[0] Component:commandLink State:[Ljava.lang.Object;@1e090ee
childState[1] Component:_id0 (This is verbatim tag) State: null
childState[2] Component:outputHeader State:[Ljava.lang.Object;@1123eb0
When the restore state process kicks in, we end up with the following:
Component:commandLink is restored with state from "childState[0]" (correct)
Component:outputHeader is restored with state from "childState[1]
** which is null ** hence, this component will not appear on postback.
this component should have been restored with "childState[2]"...
As explained in the issue comments, there are two possible fixes
for this state saving bug:
- Option 1: api code change (deal with ignoring transient children
and facets during state saving process in
UIComponentBase).
The tree state that will be saved will not include
"verbatim"
component slots.
- Option 2: ri code change (remove transient/facets from the physical tree
in StateManagerImpl for client side state saving.
This is already
done for server side state saving). This would be
done before
the API (UIComponentBase.processSaveState) is executed,
hence, end result would be the same as Option 1 - the
tree state
that will be saved will not include "verbatim"
components slots.
Keep in mind the following:
The spec (javadoc comments in the api - UIComponent.processSaveState)
mention that as each component is examined, /the "transient" property should
be consulted, and if true, return "null"; /
The spec (javadoc comments in the api - StateManager) say:
/"Components may opt out of being included in the serialized view
by setting their <code>transient</code> property to <code>true</code>.
This must cause the component itself, as well as all of that
component's
children and facets, to be omitted from the saved tree structure
and component state information."
/To me, this implies that we can also handle transient components in
the StateManager implementation.
Note: If Option 2 is used (the RI approach), then I can't think of a
reason,
that there would be transient components in the tree during
API (UIComponentBase.processSaveState) processing - I kept the
checks in because the spec mentions it...
**Option 1 Change Bundle **
M src/javax/faces/component/UIComponentBase.java
M systest/src/com/sun/faces/systest/NavigationTestCase.java
A systest/web/jsp/verbatim-one-test.jsp
A systest/web/jsp/verbatim-two-test.jsp
Index: UIComponentBase.java
===================================================================
RCS file:
/cvs/javaserverfaces-sources/jsf-api/src/javax/faces/component/UIComponentBase.java,v
retrieving revision 1.101
diff -u -r1.101 UIComponentBase.java
--- UIComponentBase.java 16 Jun 2004 00:06:55 -0000 1.101
+++ UIComponentBase.java 22 Jul 2004 15:35:27 -0000
@@ -951,20 +951,20 @@
return null;
}
Object [] stateStruct = new Object[2];
- Object [] childState = null;
// Process this component itself
stateStruct[MY_STATE] = saveState(context);
// Process all the children of this component
- int i = 0, len = getChildren().size() +
getFacets().keySet().size();
- childState = new Object[len];
- stateStruct[CHILD_STATE] = childState;
+ List nonTransientState = new ArrayList();
Iterator kids = getChildren().iterator();
while (kids.hasNext()) {
UIComponent kid = (UIComponent) kids.next();
- childState[i++] = kid.processSaveState(context);
+ if (kid.isTransient()) {
+ continue;
+ }
+ nonTransientState.add(kid.processSaveState(context));
}
Iterator myFacets = getFacets().keySet().iterator();
@@ -975,18 +975,16 @@
while (myFacets.hasNext()) {
facetName = (String) myFacets.next();
facet = (UIComponent) getFacets().get(facetName);
- if (!facet.isTransient()) {
- facetState = facet.processSaveState(context);
- facetSaveState = new Object[1][2];
- facetSaveState[0][0] = facetName;
- facetSaveState[0][1] = facetState;
- childState[i] = facetSaveState;
- }
- else {
- childState[i] = null;
+ if (facet.isTransient()) {
+ continue;
}
- i++;
+ facetState = facet.processSaveState(context);
+ facetSaveState = new Object[1][2];
+ facetSaveState[0][0] = facetName;
+ facetSaveState[0][1] = facetState;
+ nonTransientState.add(facetSaveState);
}
+ stateStruct[CHILD_STATE] = nonTransientState.toArray();
return stateStruct;
}
** Option 2 Change Bundle **
M src/com/sun/faces/application/StateManagerImpl.java
M systest/src/com/sun/faces/systest/NavigationTestCase.java
A systest/web/jsp/verbatim-one-test.jsp
A systest/web/jsp/verbatim-two-test.jsp
Index: StateManagerImpl.java
===================================================================
RCS file:
/cvs/javaserverfaces-sources/jsf-ri/src/com/sun/faces/application/StateManagerImpl.java,v
retrieving revision 1.22
diff -u -r1.22 StateManagerImpl.java
--- StateManagerImpl.java 1 Jun 2004 17:06:26 -0000 1.22
+++ StateManagerImpl.java 22 Jul 2004 15:37:16 -0000
@@ -101,6 +101,7 @@
sessionMap.put(FACES_VIEW_LIST, viewList);
}
} else {
+ removeTransientChildrenAndFacets(context, viewRoot, new
HashSet());
if (log.isDebugEnabled()) {
log.debug("Begin creating serialized view for " +
viewRoot.getViewId());
Test Case Modifications/Additions Common To
Both Options:
Index: NavigationTestCase.java
===================================================================
RCS file:
/cvs/javaserverfaces-sources/jsf-ri/systest/src/com/sun/faces/systest/NavigationTestCase.java,v
retrieving revision 1.5
diff -u -r1.5 NavigationTestCase.java
--- NavigationTestCase.java 1 May 2004 00:48:43 -0000 1.5
+++ NavigationTestCase.java 22 Jul 2004 15:40:46 -0000
@@ -120,4 +120,58 @@
assertTrue(false);
}
}
+
+ public void testNavigateWithVerbatim_One() throws Exception {
+ HtmlForm form;
+ HtmlSubmitInput submit;
+ HtmlPage page, page1;
+
+ page = getPage("/faces/jsp/verbatim-one-test.jsp");
+ form = getFormById(page, "form");
+ assertNotNull("form exists", form);
+ submit = (HtmlSubmitInput)
+ form.getInputByName("form" + NamingContainer.SEPARATOR_CHAR +
+ "submit");
+
+ // press the link, return to the same page, and check that
+ // output text (header) is still present...
+
+ try {
+ page1 = (HtmlPage) submit.click();
+ assertTrue(-1 != page1.asText().indexOf("this is the header"));
+ } catch (Exception e) {
+ e.printStackTrace();
+ assertTrue(false);
+ }
+ }
+
+ public void testNavigateWithVerbatim_Two() throws Exception {
+ HtmlForm form;
+ HtmlSubmitInput submit;
+ HtmlPage page, page1;
+
+
+ page = getPage("/faces/jsp/verbatim-two-test.jsp");
+ form = getFormById(page, "form");
+ assertNotNull("form exists", form);
+ submit = (HtmlSubmitInput)
+ form.getInputByName("form" + NamingContainer.SEPARATOR_CHAR +
+ "submit");
+
+
+ // submit the form, return to the same page, and check that
+ // output text (header) is still present...
+ // and verbatim text is still present...
+
+ try {
+ page1 = (HtmlPage) submit.click();
+ assertTrue(-1 != page1.asText().indexOf("verbatim one text
here"));
+ assertTrue(-1 != page1.asText().indexOf("this is the header"));
+ assertTrue(-1 != page1.asText().indexOf("verbatim two text
here"));
+ } catch (Exception e) {
+ e.printStackTrace();
+ assertTrue(false);
+ }
+ }
+
verbatim-one-test.jsp
<html>
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>
<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>
<f:view>
<head>
<title><h:outputText id="title" value="title"/></title>
</head>
<body>
<h:form id="form">
<h:panelGrid id="panel1" columns="2" styleClass="book"
columnClasses="menuColumn, chapterColumn">
<f:facet name="header" >
<h:panelGrid id="panel2" columns="1" >
<h:outputText id="outputheader" value="this is the header" />
<f:verbatim><hr/></f:verbatim>
</h:panelGrid>
</f:facet>
<h:commandButton id="submit" value="submit"/>
<f:verbatim >
verbatim text here
</f:verbatim>
</h:panelGrid>
</h:form>
</body>
</f:view>
</html>
verbatim-two-test.jsp
<html>
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>
<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>
<f:view>
<head>
<title><h:outputText id="title" value="title"/></title>
</head>
<body>
<h:form id="form">
<h:panelGrid id="panel1" columns="2" styleClass="book"
columnClasses="menuColumn, chapterColumn">
<f:verbatim >
verbatim one text here
</f:verbatim>
<h:panelGrid id="panel2" columns="1" >
<h:outputText id="outputheader" value="this is the header" />
<f:verbatim><hr/></f:verbatim>
</h:panelGrid>
<h:commandButton id="submit" value="submit"/>
<f:verbatim >
verbatim two text here
</f:verbatim>
</h:panelGrid>
</h:form>
</body>
</f:view>
</html>