I have implemented rolling updates with JSF 2.3. I'm using Redis (with the Redisson library) to store HTTP sessions, HAProxy as a load balancer with sticky sessions. Additionally, I am using Tomcat 8.5. When I deploy a new version, I take down the previous version of the app, and traffic is redirected to the new Tomcat with the updated app.
The issue arises when I need to update an XHTML file, for example, to add a new column to a data table. In this case, when the session is transferred from one server to another, I encounter this error:
org.primefaces.application.exceptionhandler.PrimeExceptionHandler.logException 2
java.lang.ArrayIndexOutOfBoundsException: 2
at javax.faces.component.UIComponentBase.restoreState(UIComponentBase.java:1245)
at com.sun.faces.application.view.FaceletPartialStateManagementStrategy$2.visit(FaceletPartialStateManagementStrategy.java:376)
at com.sun.faces.component.visit.FullVisitContext.invokeVisitCallback(FullVisitContext.java:127)
at javax.faces.component.UIComponent.visitTree(UIComponent.java:1457)
at javax.faces.component.UIComponent.visitTree(UIComponent.java:1469)
at javax.faces.component.UIComponent.visitTree(UIComponent.java:1469)
at javax.faces.component.UIComponent.visitTree(UIComponent.java:1469)
at javax.faces.component.UIComponent.visitTree(UIComponent.java:1469)
at javax.faces.component.UIComponent.visitTree(UIComponent.java:1469)
at javax.faces.component.UIComponent.visitTree(UIComponent.java:1469)
at javax.faces.component.UIComponent.visitTree(UIComponent.java:1469)
at javax.faces.component.UIComponent.visitTree(UIComponent.java:1469)
at com.sun.faces.application.view.FaceletPartialStateManagementStrategy.restoreView(FaceletPartialStateManagementStrategy.java:362)
at com.sun.faces.application.StateManagerImpl.restoreView(StateManagerImpl.java:113)
at com.sun.faces.application.view.ViewHandlingStrategy.restoreView(ViewHandlingStrategy.java:99)
at com.sun.faces.application.view.FaceletViewHandlingStrategy.restoreView(FaceletViewHandlingStrategy.java:272)
at com.sun.faces.application.view.MultiViewHandler.restoreView(MultiViewHandler.java:133)
at javax.faces.application.ViewHandlerWrapper.restoreView(ViewHandlerWrapper.java:101)
at javax.faces.application.ViewHandlerWrapper.restoreView(ViewHandlerWrapper.java:101)
at org.omnifaces.viewhandler.OmniViewHandler.restoreView(OmniViewHandler.java:117)
at com.sun.faces.lifecycle.RestoreViewPhase.execute(RestoreViewPhase.java:181)
at com.sun.faces.lifecycle.Phase.doPhase(Phase.java:76)
at com.sun.faces.lifecycle.RestoreViewPhase.doPhase(RestoreViewPhase.java:110)
at com.sun.faces.lifecycle.LifecycleImpl.execute(LifecycleImpl.java:177)
at javax.faces.webapp.FacesServlet.executeLifecyle(FacesServlet.java:707)
at javax.faces.webapp.FacesServlet.service(FacesServlet.java:451)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at jsf.filters.CacheFilter.doFilter(CacheFilter.java:27)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at com.filters.SecurityFilter.posResult(SecurityFilter.java:104)
at com.filters.SecurityFilter.doFilter(SecurityFilter.java:86)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.primefaces.webapp.filter.FileUploadFilter.doFilter(FileUploadFilter.java:89)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.omnifaces.filter.GzipResponseFilter.doFilter(GzipResponseFilter.java:183)
at org.omnifaces.filter.HttpFilter.doFilter(HttpFilter.java:108)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:196)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97)
at org.redisson.tomcat.UpdateValve.invoke(UpdateValve.java:62)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:542)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:135)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:81)
at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:698)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:364)
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:624)
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:831)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1650)
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191)
at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:750)
I have already tried the solution suggested in this answer (JSF Session Fail over and Partial State Saving), but I had no luck because when I change to PARTIAL_STATE_SAVING=false, I get the following error when restoring (on every page):
06-Oct-2023 16:25:25.953 SEVERE [http-nio-9090-exec-1] com.sun.faces.util.Util.checkIdUniqueness JSF1007: Duplicate component ID j_idt2 found in view.
06-Oct-2023 16:25:25.967 SEVERE [http-nio-9090-exec-1] com.sun.faces.util.Util.checkIdUniqueness +id: j_id1
type: javax.faces.component.UIViewRoot@103f414
+id: javax_faces_location_HEAD
type: com.sun.faces.component.ComponentResourceContainer@23a6f92f
+id: j_id2
type: javax.faces.component.UIOutput@23d4beea
+id: j_id3
type: javax.faces.component.UIOutput@4638b819
+id: j_id4
type: javax.faces.component.UIOutput@101099f6
+id: j_id5
This is a parcial stack because it basically says that every component on the page is duplicated.
I also tried STATE_SAVING_METHOD on the client and server. I also updated the Mojarra version to 2.3.21.
Edit: here is an example of the xhtml page
<div class="layout-main" >
<h:form id="mainForm" >
<div class="ui-g" >
<div class="ui-g-12" >
<p:commandButton value="#{msg.test}" styleClass="BtnAccionesLista Fright Wid100 "
actionListener="#{bbTest.test()}" process="@this" update=":mainForm" partialSubmit="true" />
</div>
</div>
<div class="ui-g" >
<div class="ui-g-12" >
<h:panelGroup >
<p:outputLabel value="date_from" />
<p:calendar mask="true" value="#{bbTest.dateFrom}"
showButtonPanel="true" showTodayButton="true" showOn="button" >
<p:ajax event="dateSelect" process="@this" update=":mainForm" partialSubmit="true" listener="#{bbTest.test()}" />
<p:ajax event="change" process="@this" update=":mainForm" partialSubmit="true" listener="#{bbTest.test()}" />
</p:calendar>
</h:panelGroup>
</div>
<div class="ui-g-12" >
<h:panelGroup >
<p:outputLabel value="date_to" />
<p:calendar mask="true" value="#{bbTest.dateTo}"
showButtonPanel="true" showTodayButton="true" showOn="button" >
<p:ajax event="dateSelect" process="@this" update=":mainForm" partialSubmit="true" listener="#{bbTest.test()}" />
<p:ajax event="change" process="@this" update=":mainForm" partialSubmit="true" listener="#{bbTest.test()}" />
</p:calendar>
</h:panelGroup>
</div>
</div>
<div class="ui-g" >
<div class="ui-g-12" >
<p:dataTable value="#{bbTest.list}" var="item"
paginator="true" paginatorPosition="bottom" sortBy="#{item.field1}" sortOrder="DESCENDING"
scrollable="true"
rows="15" rowsPerPageTemplate="15,25">
<p:column headerText="field1" sortable="false">
<h:outputText value="#{item.field1}" title="#{item.field1}"/>
</p:column>
<p:column headerText="field2" sortable="false">
<h:outputText value="#{item.field2}" title="#{item.field2}"/>
</p:column>
</p:dataTable>
</div>
</div>
</h:form>
</div>
@Named("bbTest")
@ViewScoped
public class BBTest implements Serializable {
private static final long serialVersionUID = 4543464259557561468L;
private Date dateFrom;
private Date dateTo;
private List<Test> list;
@PostConstruct
private void init(){
dateFrom = DateUtil.firstDay();
dateTo = DateUtil.lastDay();
completeList();
}
private void completeList(){
list = new ArrayList<>();
for(int i=0;i<30;i++){
Test test = new Test();
test.setField1("value"+i);
list.add(test);
}
}
public void test(){
System.out.println("test");
}
public List<Test> getList() {
return list;
}
public void setList(List<Test> list) {
this.list = list;
}
public Date getDateFrom() {
return dateFrom;
}
public void setDateFrom(Date dateFrom) {
this.dateFrom = dateFrom;
}
public Date getDateTo() {
return dateTo;
}
public void setDateTo(Date dateTo) {
this.dateTo = dateTo;
}
}
If I remove a column of the datatable on the new tomcat instance, then when the session is moved from one tomcat to another, I get the error.
So, my question is, in cases where I need to deploy a change in an XML file, is it possible to restore the JSF view correctly, or should I create a parallel file for existing sessions? What would be the best approach?
binding
a component to a bean property on a bean which is in a scope greater than request. Can you guarantee that you aren't doing that? See also among others stackoverflow.com/q/14911158UIComponent
instance as an instance variable then this problem can happen. But the problem is then the instance variable itself and not the bean being in view (or session) scope. Have you tried setting thejavax.faces.SERIALIZE_SERVER_STATE
context param totrue
? This should then throw an exception when there's indeed anUIComponent
instance referenced as a bean property -- it's namely not serializable.