Details
Description
This is the underlying problem for ICE-4565. It looks like, when a commandLink is pressed, that a redirect navigation rule is executed, which causes the current view to be cleaned-up:
com.icesoft.faces.context.BridgeFacesContext.resetLastViewID(BridgeFacesContext.java:839)
com.icesoft.faces.context.BridgeExternalContext.redirect(BridgeExternalContext.java:412)
com.sun.faces.application.NavigationHandlerImpl.handleNavigation(NavigationHandlerImpl.java:149)
com.sun.faces.application.ActionListenerImpl.processAction(ActionListenerImpl.java:109)
javax.faces.component.UICommand.broadcast(UICommand.java:332)
com.icesoft.faces.component.panelseries.UISeries$RowEvent.broadcast(UISeries.java:617)
The problem is that UIData/UISeries still does more processing after the ActionEvent is broadcasted. Because the UIViewRoot in BridgeFacesContext has been nulled out, then the RenderKit is no longer accessible, so the clientId calculation code throws a NPE.
java.lang.NullPointerException
javax.faces.component.UIComponentBase.getRenderer(UIComponentBase.java:1093)
javax.faces.component.UIComponentBase.getClientId(UIComponentBase.java:272)
com.icesoft.faces.component.panelseries.UISeries.restoreChild(UISeries.java:523)
com.icesoft.faces.component.panelseries.UISeries.restoreChildState(UISeries.java:475)
com.icesoft.faces.component.panelseries.UISeries.restoreChildState(UISeries.java:478)
com.icesoft.faces.component.panelseries.UISeries.restoreChildState(UISeries.java:478)
com.icesoft.faces.component.panelseries.UISeries.restoreChildrenState(UISeries.java:463)
com.icesoft.faces.component.panelseries.UISeries.setRowIndex(UISeries.java:139)
com.icesoft.faces.component.panelseries.UISeries$RowEvent.broadcast(UISeries.java:644)
com.icesoft.faces.component.panelseries.UISeries.broadcast(UISeries.java:286)
com.icesoft.faces.component.paneltabset.PanelTabSet.broadcast(PanelTabSet.java:303)
javax.faces.component.UIViewRoot.broadcastEvents(UIViewRoot.java:287)
javax.faces.component.UIViewRoot.processApplication(UIViewRoot.java:401)
com.sun.faces.lifecycle.InvokeApplicationPhase.execute(InvokeApplicationPhase.java:95)
Even if we swap out our ICEfaces container components for stock JSF ones, like h:dataTable, it still has the same problem:
java.lang.NullPointerException
javax.faces.component.UIComponentBase.getRenderer(UIComponentBase.java:1093)
javax.faces.component.UIComponentBase.getClientId(UIComponentBase.java:272)
javax.faces.component.UIData.restoreDescendantState(UIData.java:1095)
javax.faces.component.UIData.restoreDescendantState(UIData.java:1111)
javax.faces.component.UIData.restoreDescendantState(UIData.java:1071)
javax.faces.component.UIData.setRowIndex(UIData.java:416)
javax.faces.component.UIData.broadcast(UIData.java:678)
javax.faces.component.UIViewRoot.broadcastEvents(UIViewRoot.java:287)
javax.faces.component.UIViewRoot.processApplication(UIViewRoot.java:401)
com.sun.faces.lifecycle.InvokeApplicationPhase.execute(InvokeApplicationPhase.java:95)
I think that somehow we have to defer the cleanup in BridgeFacesContext.resetLastViewID() until later on, instead of doing it right in BridgeExternalContext.redirect().
But, I was able to make a simple work-around, that keeps this from being an issue within ICEfaces container components, it's just that it won't help within a stock JSF container, like h:dataTable. First, in UISeries.RowEvent.broadcast(), we omit the row state saving code when we detect the JSF lifecycle is being short-circuited:
public void broadcast() {
int oldRowIndex = getRowIndex();
setRowIndex(eventRowIndex);
event.getComponent().broadcast(event);
+ // If we're doing a navigation rule, don't alter state
+ FacesContext facesContext = FacesContext.getCurrentInstance();
+ if (facesContext == null ||
+ facesContext.getRenderResponse() ||
+ facesContext.getResponseComplete()) {
+ return;
+ }
setRowIndex(oldRowIndex);
}
Secondly, I believe I found a bug, whereby UISeries.queueEvent(FacesEvent) is redundantly having its superclass UIData do similar processing, which has to be fixed. Basically, instead of calling super.queueEvent(FacesEvent), which will call UIData.queueEvent(FacesEvent), just do what UIComponentBase.queueEvent(FacesEvent) will do, inline.
public void queueEvent(FacesEvent event) {
FacesEvent rowEvent = new RowEvent(this, event, getRowIndex());
- super.queueEvent(rowEvent);
+ UIComponent parent = getParent();
+ if (parent == null) {
+ throw new IllegalStateException();
+ } else {
+ parent.queueEvent(rowEvent);
+ }
}
com.icesoft.faces.context.BridgeFacesContext.resetLastViewID(BridgeFacesContext.java:839)
com.icesoft.faces.context.BridgeExternalContext.redirect(BridgeExternalContext.java:412)
com.sun.faces.application.NavigationHandlerImpl.handleNavigation(NavigationHandlerImpl.java:149)
com.sun.faces.application.ActionListenerImpl.processAction(ActionListenerImpl.java:109)
javax.faces.component.UICommand.broadcast(UICommand.java:332)
com.icesoft.faces.component.panelseries.UISeries$RowEvent.broadcast(UISeries.java:617)
The problem is that UIData/UISeries still does more processing after the ActionEvent is broadcasted. Because the UIViewRoot in BridgeFacesContext has been nulled out, then the RenderKit is no longer accessible, so the clientId calculation code throws a NPE.
java.lang.NullPointerException
javax.faces.component.UIComponentBase.getRenderer(UIComponentBase.java:1093)
javax.faces.component.UIComponentBase.getClientId(UIComponentBase.java:272)
com.icesoft.faces.component.panelseries.UISeries.restoreChild(UISeries.java:523)
com.icesoft.faces.component.panelseries.UISeries.restoreChildState(UISeries.java:475)
com.icesoft.faces.component.panelseries.UISeries.restoreChildState(UISeries.java:478)
com.icesoft.faces.component.panelseries.UISeries.restoreChildState(UISeries.java:478)
com.icesoft.faces.component.panelseries.UISeries.restoreChildrenState(UISeries.java:463)
com.icesoft.faces.component.panelseries.UISeries.setRowIndex(UISeries.java:139)
com.icesoft.faces.component.panelseries.UISeries$RowEvent.broadcast(UISeries.java:644)
com.icesoft.faces.component.panelseries.UISeries.broadcast(UISeries.java:286)
com.icesoft.faces.component.paneltabset.PanelTabSet.broadcast(PanelTabSet.java:303)
javax.faces.component.UIViewRoot.broadcastEvents(UIViewRoot.java:287)
javax.faces.component.UIViewRoot.processApplication(UIViewRoot.java:401)
com.sun.faces.lifecycle.InvokeApplicationPhase.execute(InvokeApplicationPhase.java:95)
Even if we swap out our ICEfaces container components for stock JSF ones, like h:dataTable, it still has the same problem:
java.lang.NullPointerException
javax.faces.component.UIComponentBase.getRenderer(UIComponentBase.java:1093)
javax.faces.component.UIComponentBase.getClientId(UIComponentBase.java:272)
javax.faces.component.UIData.restoreDescendantState(UIData.java:1095)
javax.faces.component.UIData.restoreDescendantState(UIData.java:1111)
javax.faces.component.UIData.restoreDescendantState(UIData.java:1071)
javax.faces.component.UIData.setRowIndex(UIData.java:416)
javax.faces.component.UIData.broadcast(UIData.java:678)
javax.faces.component.UIViewRoot.broadcastEvents(UIViewRoot.java:287)
javax.faces.component.UIViewRoot.processApplication(UIViewRoot.java:401)
com.sun.faces.lifecycle.InvokeApplicationPhase.execute(InvokeApplicationPhase.java:95)
I think that somehow we have to defer the cleanup in BridgeFacesContext.resetLastViewID() until later on, instead of doing it right in BridgeExternalContext.redirect().
But, I was able to make a simple work-around, that keeps this from being an issue within ICEfaces container components, it's just that it won't help within a stock JSF container, like h:dataTable. First, in UISeries.RowEvent.broadcast(), we omit the row state saving code when we detect the JSF lifecycle is being short-circuited:
public void broadcast() {
int oldRowIndex = getRowIndex();
setRowIndex(eventRowIndex);
event.getComponent().broadcast(event);
+ // If we're doing a navigation rule, don't alter state
+ FacesContext facesContext = FacesContext.getCurrentInstance();
+ if (facesContext == null ||
+ facesContext.getRenderResponse() ||
+ facesContext.getResponseComplete()) {
+ return;
+ }
setRowIndex(oldRowIndex);
}
Secondly, I believe I found a bug, whereby UISeries.queueEvent(FacesEvent) is redundantly having its superclass UIData do similar processing, which has to be fixed. Basically, instead of calling super.queueEvent(FacesEvent), which will call UIData.queueEvent(FacesEvent), just do what UIComponentBase.queueEvent(FacesEvent) will do, inline.
public void queueEvent(FacesEvent event) {
FacesEvent rowEvent = new RowEvent(this, event, getRowIndex());
- super.queueEvent(rowEvent);
+ UIComponent parent = getParent();
+ if (parent == null) {
+ throw new IllegalStateException();
+ } else {
+ parent.queueEvent(rowEvent);
+ }
}
Issue Links
- blocks
-
ICE-4565 Navigation in panelTabSet/dataTable throws NPE
- Closed
For assignment to the core team.