Skip to content

Commit 09efd70

Browse files
committed
Fix #5606: this one is a real Mojarra bug which only surfaced after CSP
backport, below is Claude's observation: Under server-side state saving, ServerSideStateHelper.writeState calls externalContext.getSession(true) at WriteBehindStateWriter.flushToWriter time. If the rendered output already exceeds the response buffer (e.g. the CSP backport in 4.0.17 emits an extra <script>mojarra.ael(...)</script> per command, roughly doubling per-link bytes), the response is committed before flushToWriter runs, getSession(true) then fails with `IllegalStateException: Cannot create a session after the response has been committed`, aborting the render mid-form, so </form> and the jakarta.faces.ViewState hidden input never reach the client. FaceletViewHandlingStrategy already had a pre-render getSession() guard for exactly this reason, but it was strict-equality on STATE_SAVING_METHOD_SERVER, which disagreed with the helper-selection rule in ResponseStateManagerImpl (anything not STATE_SAVING_METHOD_CLIENT → ServerSideStateHelper). Configurations where STATE_SAVING_METHOD is unset or contains an unresolved placeholder (e.g. ${webapp.stateSavingMethod}) silently used the server helper but skipped the pre-create. Fix isServerStateSaving() to mirror the helper-selection rule (!STATE_SAVING_METHOD_CLIENT.equalsIgnoreCase(...)), and tighten the pre-create to only fire when actually needed: non-transient view, no existing session, server-side state saving, and the view contains at least one UIForm (verified via a short-circuit visitTree). This avoids gratuitous session creation for plain pages that have no form and would not write state anyway, which previously caused JSESSIONID URL rewriting side-effects. Fixes Issue1817IT regression introduced by the CSP backport.
1 parent 846f274 commit 09efd70

1 file changed

Lines changed: 23 additions & 7 deletions

File tree

impl/src/main/java/com/sun/faces/application/view/FaceletViewHandlingStrategy.java

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@
4242
import static jakarta.faces.application.ProjectStage.Development;
4343
import static jakarta.faces.application.Resource.COMPONENT_RESOURCE_KEY;
4444
import static jakarta.faces.application.StateManager.IS_BUILDING_INITIAL_STATE;
45-
import static jakarta.faces.application.StateManager.STATE_SAVING_METHOD_SERVER;
45+
import static jakarta.faces.application.StateManager.STATE_SAVING_METHOD_CLIENT;
4646
import static jakarta.faces.application.ViewHandler.CHARACTER_ENCODING_KEY;
4747
import static jakarta.faces.application.ViewHandler.DEFAULT_FACELETS_SUFFIX;
4848
import static jakarta.faces.application.ViewVisitOption.RETURN_AS_MINIMAL_IMPLICIT_OUTCOME;
@@ -91,6 +91,7 @@
9191
import jakarta.faces.component.Doctype;
9292
import jakarta.faces.component.EditableValueHolder;
9393
import jakarta.faces.component.UIComponent;
94+
import jakarta.faces.component.UIForm;
9495
import jakarta.faces.component.UIPanel;
9596
import jakarta.faces.component.UIViewRoot;
9697
import jakarta.faces.component.html.HtmlDoctype;
@@ -410,7 +411,7 @@ public void renderView(FacesContext ctx, UIViewRoot viewToRender) throws IOExcep
410411
*
411412
* Note if you flag a view as transient then we won't acquire the session as you are stating it does not need one.
412413
*/
413-
if (isServerStateSaving() && !viewToRender.isTransient()) {
414+
if (!viewToRender.isTransient() && extContext.getSession(false) == null && isServerStateSaving() && hasForm(ctx, viewToRender)) {
414415
getSession(ctx);
415416
}
416417

@@ -1834,11 +1835,7 @@ private void reapplyDynamicRemove(FacesContext context, ComponentStruct struct)
18341835
* @return true if we are, false otherwise.
18351836
*/
18361837
private boolean isServerStateSaving() {
1837-
if (STATE_SAVING_METHOD_SERVER.equals(webConfig.getOptionValue(StateSavingMethod))) {
1838-
return true;
1839-
}
1840-
1841-
return false;
1838+
return !STATE_SAVING_METHOD_CLIENT.equalsIgnoreCase(webConfig.getOptionValue(StateSavingMethod));
18421839
}
18431840

18441841
/**
@@ -1856,6 +1853,25 @@ private HttpSession getSession(FacesContext context) {
18561853
return null;
18571854
}
18581855

1856+
/**
1857+
* @return true if the view contains at least one {@link UIForm}, in which case state will be written and a session
1858+
* is needed under server-side state saving.
1859+
*/
1860+
private static boolean hasForm(FacesContext context, UIViewRoot viewRoot) {
1861+
if (viewRoot == null || viewRoot.getChildCount() == 0) {
1862+
return false;
1863+
}
1864+
boolean[] found = { false };
1865+
viewRoot.visitTree(VisitContext.createVisitContext(context), (visitContext, target) -> {
1866+
if (target instanceof UIForm) {
1867+
found[0] = true;
1868+
return VisitResult.COMPLETE;
1869+
}
1870+
return VisitResult.ACCEPT;
1871+
});
1872+
return found[0];
1873+
}
1874+
18591875
/**
18601876
* Gets and if needed initializes the faceletFactory
18611877
*

0 commit comments

Comments
 (0)