I am developing a JSF custom component. This component has the purpose of encapsulating another component (namely a PrimeFaces table) and adding customized behaviour to it. For example, one of the features that it supports is creating PrimeFaces columns dynamically from the underlying data or from certain attributes. Furthermore, it supports declaring additional PrimeFaces columns in XHTML, which should be added to the encapsulated PrimeFaces table as well.
Consider this example:
<my:table id="table" fields="title,label,value,additional">
<primefaces:column id="additional">
some content
</primefaces:column>
</my:table>
My custom component dynamically creates the PrimeFaces columns from the fields attribute during rendering. It then moves all its column children to the PrimeFaces table, so after rendering the component tree looks like follows:
my:table id="table"
|---primefaces:table id="table_table"
|---primefaces:column id="title"
|---primefaces:column id="label"
|---primefaces:column id="value"
|---primefaces:column id="additional"
During first rendering, this works fine. However, when I then perform an AJAX update of my component, I get the following exception:
javax.faces.FacesException: Cannot remove the same component twice: table:additional
at com.sun.faces.context.StateContext$DynamicAddRemoveListener.handleAddRemoveWithAutoPrune(StateContext.java:761)
at com.sun.faces.context.StateContext$DynamicAddRemoveListener.handleRemove(StateContext.java:629)
at com.sun.faces.context.StateContext$AddRemoveListener.processEvent(StateContext.java:342)
at com.sun.faces.context.StateContext$DynamicAddRemoveListener.processEvent(StateContext.java:565)
at javax.faces.event.SystemEvent.processListener(SystemEvent.java:108)
at javax.faces.event.ComponentSystemEvent.processListener(ComponentSystemEvent.java:118)
at com.sun.faces.application.ApplicationImpl.processListenersAccountingForAdds(ApplicationImpl.java:2218)
at com.sun.faces.application.ApplicationImpl.invokeViewListenersFor(ApplicationImpl.java:2036)
at com.sun.faces.application.ApplicationImpl.publishEvent(ApplicationImpl.java:290)
at com.sun.faces.application.ApplicationImpl.publishEvent(ApplicationImpl.java:245)
at javax.faces.application.ApplicationWrapper.publishEvent(ApplicationWrapper.java:726)
at javax.faces.component.UIComponentBase.disconnectFromView(UIComponentBase.java:2275)
at javax.faces.component.UIComponentBase.doPreRemoveProcessing(UIComponentBase.java:1939)
at javax.faces.component.UIComponentBase.setParent(UIComponentBase.java:437)
at javax.faces.component.UIComponentBase$ChildrenList.remove(UIComponentBase.java:2757)
at com.sun.faces.facelets.tag.jsf.ComponentTagHandlerDelegateImpl.adjustIndexOfDynamicChildren(ComponentTagHandlerDelegateImpl.java:283)
at com.sun.faces.facelets.tag.jsf.ComponentTagHandlerDelegateImpl.apply(ComponentTagHandlerDelegateImpl.java:223)
at javax.faces.view.facelets.DelegatingMetaTagHandler.apply(DelegatingMetaTagHandler.java:120)
at javax.faces.view.facelets.CompositeFaceletHandler.apply(CompositeFaceletHandler.java:95)
at com.sun.faces.facelets.tag.ui.DefineHandler.applyDefinition(DefineHandler.java:106)
at com.sun.faces.facelets.tag.ui.CompositionHandler.apply(CompositionHandler.java:206)
at com.sun.faces.facelets.impl.DefaultFaceletContext$TemplateManager.apply(DefaultFaceletContext.java:395)
at com.sun.faces.facelets.impl.DefaultFaceletContext.includeDefinition(DefaultFaceletContext.java:366)
at com.sun.faces.facelets.tag.ui.InsertHandler.apply(InsertHandler.java:111)
at javax.faces.view.facelets.DelegatingMetaTagHandler.applyNextHandler(DelegatingMetaTagHandler.java:137)
at com.sun.faces.facelets.tag.jsf.ComponentTagHandlerDelegateImpl.apply(ComponentTagHandlerDelegateImpl.java:202)
at javax.faces.view.facelets.DelegatingMetaTagHandler.apply(DelegatingMetaTagHandler.java:120)
at javax.faces.view.facelets.CompositeFaceletHandler.apply(CompositeFaceletHandler.java:95)
at com.sun.faces.facelets.tag.ui.CompositionHandler.apply(CompositionHandler.java:194)
at com.sun.faces.facelets.compiler.NamespaceHandler.apply(NamespaceHandler.java:93)
at com.sun.faces.facelets.compiler.EncodingHandler.apply(EncodingHandler.java:87)
at com.sun.faces.facelets.impl.DefaultFacelet.include(DefaultFacelet.java:312)
at com.sun.faces.facelets.impl.DefaultFacelet.include(DefaultFacelet.java:371)
at com.sun.faces.facelets.impl.DefaultFacelet.include(DefaultFacelet.java:350)
at com.sun.faces.facelets.impl.DefaultFaceletContext.includeFacelet(DefaultFaceletContext.java:199)
at com.sun.faces.facelets.tag.ui.IncludeHandler.apply(IncludeHandler.java:124)
at com.sun.faces.facelets.tag.ui.InsertHandler.apply(InsertHandler.java:116)
at javax.faces.view.facelets.CompositeFaceletHandler.apply(CompositeFaceletHandler.java:95)
at javax.faces.view.facelets.DelegatingMetaTagHandler.applyNextHandler(DelegatingMetaTagHandler.java:137)
at com.sun.faces.facelets.tag.jsf.ComponentTagHandlerDelegateImpl.apply(ComponentTagHandlerDelegateImpl.java:202)
at javax.faces.view.facelets.DelegatingMetaTagHandler.apply(DelegatingMetaTagHandler.java:120)
at javax.faces.view.facelets.CompositeFaceletHandler.apply(CompositeFaceletHandler.java:95)
at javax.faces.view.facelets.DelegatingMetaTagHandler.applyNextHandler(DelegatingMetaTagHandler.java:137)
at com.sun.faces.facelets.tag.jsf.ComponentTagHandlerDelegateImpl.apply(ComponentTagHandlerDelegateImpl.java:202)
at javax.faces.view.facelets.DelegatingMetaTagHandler.apply(DelegatingMetaTagHandler.java:120)
at javax.faces.view.facelets.CompositeFaceletHandler.apply(CompositeFaceletHandler.java:95)
at com.sun.faces.facelets.tag.ui.DefineHandler.applyDefinition(DefineHandler.java:106)
at com.sun.faces.facelets.tag.ui.CompositionHandler.apply(CompositionHandler.java:206)
at com.sun.faces.facelets.impl.DefaultFaceletContext$TemplateManager.apply(DefaultFaceletContext.java:395)
at com.sun.faces.facelets.impl.DefaultFaceletContext.includeDefinition(DefaultFaceletContext.java:366)
at com.sun.faces.facelets.tag.ui.InsertHandler.apply(InsertHandler.java:111)
at javax.faces.view.facelets.CompositeFaceletHandler.apply(CompositeFaceletHandler.java:95)
at javax.faces.view.facelets.DelegatingMetaTagHandler.applyNextHandler(DelegatingMetaTagHandler.java:137)
at com.sun.faces.facelets.tag.jsf.ComponentTagHandlerDelegateImpl.apply(ComponentTagHandlerDelegateImpl.java:202)
at javax.faces.view.facelets.DelegatingMetaTagHandler.apply(DelegatingMetaTagHandler.java:120)
at javax.faces.view.facelets.CompositeFaceletHandler.apply(CompositeFaceletHandler.java:95)
at javax.faces.view.facelets.DelegatingMetaTagHandler.applyNextHandler(DelegatingMetaTagHandler.java:137)
at com.sun.faces.facelets.tag.jsf.ComponentTagHandlerDelegateImpl.apply(ComponentTagHandlerDelegateImpl.java:202)
at javax.faces.view.facelets.DelegatingMetaTagHandler.apply(DelegatingMetaTagHandler.java:120)
at javax.faces.view.facelets.CompositeFaceletHandler.apply(CompositeFaceletHandler.java:95)
at com.sun.faces.facelets.tag.jsf.core.ViewHandler.apply(ViewHandler.java:225)
at javax.faces.view.facelets.CompositeFaceletHandler.apply(CompositeFaceletHandler.java:95)
at javax.faces.view.facelets.DelegatingMetaTagHandler.applyNextHandler(DelegatingMetaTagHandler.java:137)
at com.sun.faces.facelets.tag.jsf.ComponentTagHandlerDelegateImpl.apply(ComponentTagHandlerDelegateImpl.java:202)
at javax.faces.view.facelets.DelegatingMetaTagHandler.apply(DelegatingMetaTagHandler.java:120)
at javax.faces.view.facelets.CompositeFaceletHandler.apply(CompositeFaceletHandler.java:95)
at com.sun.faces.facelets.compiler.NamespaceHandler.apply(NamespaceHandler.java:93)
at com.sun.faces.facelets.compiler.EncodingHandler.apply(EncodingHandler.java:87)
at com.sun.faces.facelets.impl.DefaultFacelet.include(DefaultFacelet.java:312)
at com.sun.faces.facelets.impl.DefaultFacelet.include(DefaultFacelet.java:371)
at com.sun.faces.facelets.impl.DefaultFacelet.include(DefaultFacelet.java:350)
at com.sun.faces.facelets.impl.DefaultFaceletContext.includeFacelet(DefaultFaceletContext.java:199)
at com.sun.faces.facelets.tag.ui.CompositionHandler.apply(CompositionHandler.java:174)
at com.sun.faces.facelets.compiler.NamespaceHandler.apply(NamespaceHandler.java:93)
at com.sun.faces.facelets.compiler.EncodingHandler.apply(EncodingHandler.java:87)
at com.sun.faces.facelets.impl.DefaultFacelet.include(DefaultFacelet.java:312)
at com.sun.faces.facelets.impl.DefaultFacelet.include(DefaultFacelet.java:371)
at com.sun.faces.facelets.impl.DefaultFacelet.include(DefaultFacelet.java:350)
at com.sun.faces.facelets.impl.DefaultFaceletContext.includeFacelet(DefaultFaceletContext.java:199)
at com.sun.faces.facelets.tag.ui.CompositionHandler.apply(CompositionHandler.java:174)
at com.sun.faces.facelets.compiler.NamespaceHandler.apply(NamespaceHandler.java:93)
at com.sun.faces.facelets.compiler.EncodingHandler.apply(EncodingHandler.java:87)
at com.sun.faces.facelets.impl.DefaultFacelet.apply(DefaultFacelet.java:161)
at com.sun.faces.application.view.FaceletViewHandlingStrategy.buildView(FaceletViewHandlingStrategy.java:1006)
at com.sun.faces.lifecycle.RenderResponsePhase.execute(RenderResponsePhase.java:99)
at com.sun.faces.lifecycle.Phase.doPhase(Phase.java:101)
at com.sun.faces.lifecycle.LifecycleImpl.render(LifecycleImpl.java:219)
at javax.faces.webapp.FacesServlet.service(FacesServlet.java:647)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:230)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165)
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165)
at de.gebit.trend.servlet.security.AuthorizationFilter.doFilter(AuthorizationFilter.java:269)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:198)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:108)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:472)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:140)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:79)
at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:620)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:87)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:349)
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:783)
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66)
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:789)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1455)
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Unknown Source)
So, only the column that was moved from my table to the PrimeFaces table is creating this error. The other columns do not get recreated, because during rendering, I use an instance variable stored in the StateHelper to indicate that columns are already created.
I somehow understand where this exception is coming from and that it has something to do with JSF saving the complete component tree, and when JSF restores the view, the saved state is inconsistent with the XHTML. What I don't know is, how to solve this problem.
Could someone please explain to me how this state saving mechanism exactly works, especially in conjunction with dynamically added children and how to avoid this exception?
UPDATE (10.02.2017)
I've created a small sample project without additional frameworks that were in use before. It can be found on my GitHub profile. One main framework that was in use before was fiddeling with the AddRemoveListeners installed in the StateContext for replaying dynamic actions. To avoid this having an impact on my issue and to create a reproducible environment, I removed them.
The behavior I witness now is slightly different (there are no exceptions anymore) and depends on whether partial state saving is enabled/disabled and the method I use to move the primefaces:column:
The first rendering of the table in all cases works fine. I then perform a postback request by submitting a paging request. The behavior then is erronous in some cases.
Partial state saving enabled
With partial state saving enabled, paging does not work. I do not get an exception, but a lot of warnings similar to this:
Feb 10, 2017 4:33:11 PM com.sun.faces.application.view.FaceletPartialStateManagementStrategy saveDynamicActions
WARNUNG: Unable to save dynamic action with clientId 'form:table:table_table:additional' because the UIComponent cannot be found
This warning appears for every component that was moved dynamically created in or moved to the primefaces:table or was a child component of one of those.
Partial state saving disabled
With partial state saving disabled, paging does work but shows different behavior depending on when the one custom primefaces:column is moved.
Moving of `column` during render response phase
When the primefaces:column is moved during render response phase, e.g. in encodeXxx, everything works fine. All the columns are in the correct order and with correct values and paging works perfectly fine.
Moving of `column` using a `PostAddToViewEvent`
When using this approach, as per @BalusC suggestion, the moved primefaces:column disappears on paging. The PostAddToViewEvent is called several times and the column is moved while handling this event, however on rendering, it has disappeared and only the three previously created columns are still there.
At this point, I am more than confused. Is this a bug in Mojarra or in Primefaces or am I doing something wrong? Is this kind of behavior even possible with JSF?