diff --git a/impl/src/main/java/com/sun/faces/application/view/FaceletViewHandlingStrategy.java b/impl/src/main/java/com/sun/faces/application/view/FaceletViewHandlingStrategy.java index 3b0dd8052d..43cd8e1519 100644 --- a/impl/src/main/java/com/sun/faces/application/view/FaceletViewHandlingStrategy.java +++ b/impl/src/main/java/com/sun/faces/application/view/FaceletViewHandlingStrategy.java @@ -43,6 +43,7 @@ import static jakarta.faces.application.Resource.COMPONENT_RESOURCE_KEY; import static jakarta.faces.application.StateManager.IS_BUILDING_INITIAL_STATE; import static jakarta.faces.application.StateManager.STATE_SAVING_METHOD_SERVER; +import static jakarta.faces.application.ViewHandler.CHARACTER_ENCODING_KEY; import static jakarta.faces.application.ViewHandler.DEFAULT_FACELETS_SUFFIX; import static jakarta.faces.application.ViewVisitOption.RETURN_AS_MINIMAL_IMPLICIT_OUTCOME; import static jakarta.faces.component.UIComponent.BEANINFO_KEY; @@ -57,7 +58,6 @@ import static java.util.Collections.emptyList; import static java.util.logging.Level.FINE; import static java.util.logging.Level.FINEST; -import static java.util.logging.Level.SEVERE; import static java.util.logging.Level.WARNING; import java.beans.BeanDescriptor; @@ -71,33 +71,12 @@ import java.util.Iterator; import java.util.List; import java.util.Map; -import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; import java.util.logging.Level; import java.util.logging.Logger; import java.util.stream.Stream; -import com.sun.faces.application.ApplicationAssociate; -import com.sun.faces.config.WebConfiguration; -import com.sun.faces.context.StateContext; -import com.sun.faces.facelets.compiler.FaceletDoctype; -import com.sun.faces.facelets.el.ContextualCompositeMethodExpression; -import com.sun.faces.facelets.el.VariableMapperWrapper; -import com.sun.faces.facelets.impl.DefaultFaceletFactory; -import com.sun.faces.facelets.impl.XMLFrontMatterSaver; -import com.sun.faces.facelets.tag.composite.CompositeComponentBeanInfo; -import com.sun.faces.facelets.tag.faces.CompositeComponentTagHandler; -import com.sun.faces.facelets.tag.ui.UIDebug; -import com.sun.faces.renderkit.RenderKitUtils; -import com.sun.faces.renderkit.html_basic.DoctypeRenderer; -import com.sun.faces.util.Cache; -import com.sun.faces.util.ComponentStruct; -import com.sun.faces.util.FacesLogger; -import com.sun.faces.util.HtmlUtils; -import com.sun.faces.util.RequestStateManager; -import com.sun.faces.util.Util; - import jakarta.el.ELContext; import jakarta.el.ExpressionFactory; import jakarta.el.MethodExpression; @@ -145,6 +124,26 @@ import jakarta.faces.view.facelets.FaceletContext; import jakarta.servlet.http.HttpSession; +import com.sun.faces.application.ApplicationAssociate; +import com.sun.faces.config.WebConfiguration; +import com.sun.faces.context.StateContext; +import com.sun.faces.facelets.compiler.FaceletDoctype; +import com.sun.faces.facelets.el.ContextualCompositeMethodExpression; +import com.sun.faces.facelets.el.VariableMapperWrapper; +import com.sun.faces.facelets.impl.DefaultFaceletFactory; +import com.sun.faces.facelets.impl.XMLFrontMatterSaver; +import com.sun.faces.facelets.tag.composite.CompositeComponentBeanInfo; +import com.sun.faces.facelets.tag.faces.CompositeComponentTagHandler; +import com.sun.faces.facelets.tag.ui.UIDebug; +import com.sun.faces.renderkit.RenderKitUtils; +import com.sun.faces.renderkit.html_basic.DoctypeRenderer; +import com.sun.faces.util.Cache; +import com.sun.faces.util.ComponentStruct; +import com.sun.faces.util.FacesLogger; +import com.sun.faces.util.HtmlUtils; +import com.sun.faces.util.RequestStateManager; +import com.sun.faces.util.Util; + /** * This {@link ViewHandlingStrategy} handles Facelets/PDL-based views. */ @@ -905,19 +904,20 @@ protected ResponseWriter createResponseWriter(FacesContext context) throws IOExc } } - // get our content type - String contentType = (String) context.getAttributes().get("facelets.ContentType"); + // Get the as default content type. + // See also ViewHandler#apply(). + String defaultContentType = (String) context.getAttributes().get("facelets.ContentType"); - // get the encoding - String encoding = (String) context.getAttributes().get(FACELETS_ENCODING_KEY); + // Get the or otherwise Facelets default encoding of UTF-8 as default encoding. + // See also SAXCompiler#doCompile() and EncodingHandler#apply(). + String defaultEncoding = (String) context.getAttributes().get(FACELETS_ENCODING_KEY); - // Create a dummy ResponseWriter with a bogus writer, - // so we can figure out what content type and encoding the ReponseWriter - // is really going to ask for - ResponseWriter initWriter = renderKit.createResponseWriter(NullWriter.INSTANCE, contentType, encoding); + // Create a dummy ResponseWriter with a bogus writer, so we can figure out what + // content type and default encoding the ResponseWriter is ultimately going to need. + ResponseWriter initWriter = renderKit.createResponseWriter(NullWriter.INSTANCE, defaultContentType, defaultEncoding); - contentType = getResponseContentType(context, initWriter.getContentType()); - encoding = Util.getResponseEncoding(context, Optional.ofNullable(initWriter.getCharacterEncoding())); + String contentType = getResponseContentType(context, initWriter.getContentType()); + String encoding = Util.getResponseEncoding(context, initWriter.getCharacterEncoding()); // apply them to the response char[] buffer = new char[1028]; @@ -929,6 +929,11 @@ protected ResponseWriter createResponseWriter(FacesContext context) throws IOExc // Save encoding in UIViewRoot for faster consult when Util#getResponseEncoding() is invoked again elsewhere. context.getViewRoot().getAttributes().put(FACELETS_ENCODING_KEY, encoding); + // Save encoding in Session for consult in subsequent postback request as per spec section "2.5.2.2. Determining the Character Encoding". + if (context.getExternalContext().getSession(false) != null) { + context.getExternalContext().getSessionMap().put(CHARACTER_ENCODING_KEY, encoding); + } + // Now, clone with the real writer ResponseWriter writer = initWriter.cloneWithWriter(extContext.getResponseOutputWriter()); diff --git a/impl/src/main/java/com/sun/faces/util/Util.java b/impl/src/main/java/com/sun/faces/util/Util.java index ca481492aa..feebf087f3 100644 --- a/impl/src/main/java/com/sun/faces/util/Util.java +++ b/impl/src/main/java/com/sun/faces/util/Util.java @@ -77,13 +77,6 @@ import javax.xml.xpath.XPathExpressionException; import javax.xml.xpath.XPathFactory; -import com.sun.faces.RIConstants; -import com.sun.faces.application.ApplicationAssociate; -import com.sun.faces.config.WebConfiguration; -import com.sun.faces.config.manager.FacesSchema; -import com.sun.faces.facelets.component.UIRepeat; -import com.sun.faces.io.FastStringWriter; - import jakarta.el.ELResolver; import jakarta.el.ValueExpression; import jakarta.enterprise.inject.spi.BeanManager; @@ -111,6 +104,13 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.MappingMatch; +import com.sun.faces.RIConstants; +import com.sun.faces.application.ApplicationAssociate; +import com.sun.faces.config.WebConfiguration; +import com.sun.faces.config.manager.FacesSchema; +import com.sun.faces.facelets.component.UIRepeat; +import com.sun.faces.io.FastStringWriter; + /** * Util is a class ... * @@ -1626,7 +1626,7 @@ public static int extractFirstNumericSegment(String clientId, char separatorChar * @return the encoding to be used for the response */ public static String getResponseEncoding(FacesContext context) { - return getResponseEncoding(context, Optional.empty()); + return getResponseEncoding(context, null); } /** @@ -1634,7 +1634,7 @@ public static String getResponseEncoding(FacesContext context) { * @param defaultEncoding the default encoding, if any * @return the encoding to be used for the response */ - public static String getResponseEncoding(FacesContext context, Optional defaultEncoding) { + public static String getResponseEncoding(FacesContext context, String defaultEncoding) { // 1. First get it from viewroot, if any. if (context.getViewRoot() != null) { @@ -1666,8 +1666,8 @@ public static String getResponseEncoding(FacesContext context, Optional } if (encoding == null && context.getExternalContext().getSession(false) != null) { - // 4. If still none found then get previously known request encoding from session. - // See also ViewHandler#initView(). + // 4. If still none found then get previously known request or response encoding from session. + // See also ViewHandler#initView() and FaceletViewHandlingStrategy#createResponseWriter(). encoding = (String) context.getExternalContext().getSessionMap().get(CHARACTER_ENCODING_KEY); if (encoding != null && LOGGER.isLoggable(FINEST)) { @@ -1677,9 +1677,7 @@ public static String getResponseEncoding(FacesContext context, Optional if (encoding == null) { // 5. If still none found then fall back to specified default. - if (defaultEncoding.isPresent()) { - encoding = defaultEncoding.get(); - } + encoding = defaultEncoding; if (encoding != null && !encoding.isBlank()) { if (LOGGER.isLoggable(FINEST)) { diff --git a/test/issue5543/pom.xml b/test/issue5543/pom.xml new file mode 100644 index 0000000000..afd776a7cf --- /dev/null +++ b/test/issue5543/pom.xml @@ -0,0 +1,41 @@ + + + + 4.0.0 + + + org.eclipse.mojarra.test + pom + 4.0.10-SNAPSHOT + + + issue5543 + war + + Mojarra ${project.version} - INTEGRATION TESTS - ${project.artifactId} + + + + org.eclipse.mojarra.test + base + ${project.version} + test + + + diff --git a/test/issue5543/src/main/java/org/eclipse/mojarra/test/issue5543/Issue5543Bean.java b/test/issue5543/src/main/java/org/eclipse/mojarra/test/issue5543/Issue5543Bean.java new file mode 100644 index 0000000000..ed450600c6 --- /dev/null +++ b/test/issue5543/src/main/java/org/eclipse/mojarra/test/issue5543/Issue5543Bean.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) Contributors to the Eclipse Foundation. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GPL-2.0 with Classpath-exception-2.0 which + * is available at https://openjdk.java.net/legal/gplv2+ce.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 or Apache-2.0 + */ +package org.eclipse.mojarra.test.issue5543; + +import jakarta.enterprise.context.RequestScoped; +import jakarta.inject.Named; + +@Named +@RequestScoped +public class Issue5543Bean { + + private String text = "ä"; + + public String getText() { + return text; + } + + public void setText(String text) { + this.text = text; + } +} diff --git a/test/issue5543/src/main/webapp/WEB-INF/web.xml b/test/issue5543/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 0000000000..8784690178 --- /dev/null +++ b/test/issue5543/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,34 @@ + + + + + facesServlet + jakarta.faces.webapp.FacesServlet + 1 + + + facesServlet + *.xhtml + + \ No newline at end of file diff --git a/test/issue5543/src/main/webapp/issue5543.xhtml b/test/issue5543/src/main/webapp/issue5543.xhtml new file mode 100644 index 0000000000..a5683d231b --- /dev/null +++ b/test/issue5543/src/main/webapp/issue5543.xhtml @@ -0,0 +1,35 @@ + + + + + issue5543 + + + + + + + + + + diff --git a/test/issue5543/src/test/java/org/eclipse/mojarra/test/issue5543/Issue5543IT.java b/test/issue5543/src/test/java/org/eclipse/mojarra/test/issue5543/Issue5543IT.java new file mode 100644 index 0000000000..7ac16b6292 --- /dev/null +++ b/test/issue5543/src/test/java/org/eclipse/mojarra/test/issue5543/Issue5543IT.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) Contributors to the Eclipse Foundation. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GPL-2.0 with Classpath-exception-2.0 which + * is available at https://openjdk.java.net/legal/gplv2+ce.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 or Apache-2.0 + */ +package org.eclipse.mojarra.test.issue5543; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.eclipse.mojarra.test.base.BaseIT; +import org.junit.jupiter.api.Test; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.support.FindBy; + +class Issue5543IT extends BaseIT { + + @FindBy(id = "form:input") + private WebElement input; + + @FindBy(id = "form:nonAjaxSubmit") + private WebElement nonAjaxSubmit; + + @FindBy(id = "form:ajaxSubmit") + private WebElement ajaxSubmit; + + @FindBy(id = "form:output") + private WebElement output; + + /** + * https://github.com/eclipse-ee4j/mojarra/issues/5543 + */ + @Test + void testDefaultResponseEncodingNonAjax() { + open("issue5543.xhtml"); + assertEquals("ä", input.getAttribute("value")); + assertEquals("ä", output.getText()); + guardHttp(nonAjaxSubmit::click); + assertEquals("ä", input.getAttribute("value")); + assertEquals("ä", output.getText()); + } + + /** + * https://github.com/eclipse-ee4j/mojarra/issues/5543 + */ + @Test + void testDefaultResponseEncodingAjax() { + open("issue5543.xhtml"); + assertEquals("ä", input.getAttribute("value")); + assertEquals("ä", output.getText()); + guardAjax(ajaxSubmit::click); + assertEquals("ä", input.getAttribute("value")); + assertEquals("ä", output.getText()); + } +} diff --git a/test/pom.xml b/test/pom.xml index 986f43ef33..12f29bcd02 100644 --- a/test/pom.xml +++ b/test/pom.xml @@ -42,6 +42,7 @@ issue5507 issue5511 issue5541 + issue5543