diff --git a/impl/src/main/java/com/sun/faces/RIConstants.java b/impl/src/main/java/com/sun/faces/RIConstants.java index 62b400c6b4..cffc4e0ec5 100644 --- a/impl/src/main/java/com/sun/faces/RIConstants.java +++ b/impl/src/main/java/com/sun/faces/RIConstants.java @@ -17,6 +17,8 @@ package com.sun.faces; +import java.nio.charset.StandardCharsets; + import com.sun.faces.config.manager.FacesSchema; import jakarta.faces.render.RenderKitFactory; @@ -58,7 +60,7 @@ public class RIConstants { public static final String APPLICATION_XML_CONTENT_TYPE = "application/xml"; public static final String TEXT_XML_CONTENT_TYPE = "text/xml"; public static final String ALL_MEDIA = "*/*"; - public static final String CHAR_ENCODING = "UTF-8"; + public static final String CHAR_ENCODING = StandardCharsets.UTF_8.name(); public static final String FACELETS_ENCODING_KEY = "facelets.Encoding"; public static final String DEFAULT_LIFECYCLE = FACES_PREFIX + "DefaultLifecycle"; public static final String DEFAULT_STATEMANAGER = FACES_PREFIX + "DefaultStateManager"; diff --git a/impl/src/main/java/com/sun/faces/application/ApplicationAssociate.java b/impl/src/main/java/com/sun/faces/application/ApplicationAssociate.java index e08430df88..4bdabc4f1b 100644 --- a/impl/src/main/java/com/sun/faces/application/ApplicationAssociate.java +++ b/impl/src/main/java/com/sun/faces/application/ApplicationAssociate.java @@ -196,9 +196,12 @@ public static ApplicationAssociate getCurrentInstance() { } public static ApplicationAssociate getInstance() { - FacesContext facesContext = FacesContext.getCurrentInstance(); + return getInstance(FacesContext.getCurrentInstance()); + } + + public static ApplicationAssociate getInstance(FacesContext facesContext) { if (facesContext == null) { - return null; + return null; } return ApplicationAssociate.getInstance(facesContext.getExternalContext()); diff --git a/impl/src/main/java/com/sun/faces/application/ConverterPropertyEditorFactory.java b/impl/src/main/java/com/sun/faces/application/ConverterPropertyEditorFactory.java index f5a824a2e6..c1b4c7dc21 100644 --- a/impl/src/main/java/com/sun/faces/application/ConverterPropertyEditorFactory.java +++ b/impl/src/main/java/com/sun/faces/application/ConverterPropertyEditorFactory.java @@ -16,6 +16,7 @@ package com.sun.faces.application; +import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.logging.Level.FINE; import static java.util.logging.Level.FINEST; import static java.util.logging.Level.WARNING; @@ -23,9 +24,7 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; -import java.io.UnsupportedEncodingException; import java.lang.ref.WeakReference; -import java.nio.charset.StandardCharsets; import java.security.AccessController; import java.security.PrivilegedAction; import java.util.HashMap; @@ -81,7 +80,6 @@ private static class Utf8InfoRef { int length; public Utf8InfoRef(int index, int length) { - super(); this.index = index; this.length = length; } @@ -103,7 +101,6 @@ private static class Utf8InfoReplacement implements Comparable c) { * @return the bytes for the UTF8Info constant pool entry, including the tag, length, and utf8 content. */ private static byte[] getUtf8InfoBytes(String text) { - byte[] utf8; - utf8 = text.getBytes(StandardCharsets.UTF_8); + byte[] utf8 = text.getBytes(UTF_8); byte[] info = new byte[utf8.length + 3]; info[0] = 1; info[1] = (byte) (utf8.length >> 8 & 0xff); diff --git a/impl/src/main/java/com/sun/faces/application/resource/FaceletWebappResourceHelper.java b/impl/src/main/java/com/sun/faces/application/resource/FaceletWebappResourceHelper.java index 513fbb8cc7..33f708b208 100644 --- a/impl/src/main/java/com/sun/faces/application/resource/FaceletWebappResourceHelper.java +++ b/impl/src/main/java/com/sun/faces/application/resource/FaceletWebappResourceHelper.java @@ -19,7 +19,6 @@ import static com.sun.faces.RIConstants.FLOW_IN_JAR_PREFIX; import static com.sun.faces.config.WebConfiguration.META_INF_CONTRACTS_DIR; import static com.sun.faces.config.WebConfiguration.WebContextInitParameter.FaceletsSuffix; -import static com.sun.faces.config.WebConfiguration.WebContextInitParameter.WebAppContractsDirectory; import static jakarta.faces.application.ResourceVisitOption.TOP_LEVEL_VIEWS_ONLY; import static java.util.Spliterator.DISTINCT; import static java.util.Spliterators.spliteratorUnknownSize; @@ -32,13 +31,16 @@ import java.util.Enumeration; import java.util.List; import java.util.NoSuchElementException; +import java.util.Objects; import java.util.stream.Stream; import com.sun.faces.application.ApplicationAssociate; import com.sun.faces.config.WebConfiguration; import com.sun.faces.util.Util; +import jakarta.enterprise.inject.Any; import jakarta.faces.FacesException; +import jakarta.faces.annotation.View; import jakarta.faces.application.ResourceVisitOption; import jakarta.faces.context.ExternalContext; import jakarta.faces.context.FacesContext; @@ -48,12 +50,12 @@ public class FaceletWebappResourceHelper extends ResourceHelper { private static final String[] RESTRICTED_DIRECTORIES = { "/WEB-INF/", "/META-INF/" }; - private final String webAppContractsDirectory; + private final ResourceHelper webappResourceHelper; private final String[] configuredExtensions; - public FaceletWebappResourceHelper() { + public FaceletWebappResourceHelper(WebappResourceHelper webappResourceHelper) { + this.webappResourceHelper = webappResourceHelper; WebConfiguration webConfig = WebConfiguration.getInstance(); - webAppContractsDirectory = webConfig.getOptionValue(WebAppContractsDirectory); configuredExtensions = webConfig.getOptionValue(FaceletsSuffix, " "); } @@ -114,9 +116,16 @@ public ResourceInfo findResource(LibraryInfo library, String resourceName, Strin } public Stream getViewResources(FacesContext facesContext, String path, int maxDepth, ResourceVisitOption... options) { - return stream(spliteratorUnknownSize( + Stream physicalViewResources = stream(spliteratorUnknownSize( new ResourcePathsIterator(path, maxDepth, configuredExtensions, getRestrictedDirectories(options), facesContext.getExternalContext()), DISTINCT), false); + Stream programmaticViewResources = Util.getCdiBeanManager(facesContext) + .getBeans(Object.class, Any.Literal.INSTANCE).stream() + .map(bean -> bean.getBeanClass().getAnnotation(View.class)) + .filter(Objects::nonNull) + .map(View::value); + + return Stream.concat(physicalViewResources, programmaticViewResources); } private static String[] getRestrictedDirectories(final ResourceVisitOption... options) { @@ -150,9 +159,9 @@ private URL findResourceInfoConsideringContracts(FacesContext ctx, String baseRe for (String contract : contracts) { if (baseResourceName.startsWith("/")) { - resourceName = webAppContractsDirectory + "/" + contract + baseResourceName; + resourceName = getBaseContractsPath() + "/" + contract + baseResourceName; } else { - resourceName = webAppContractsDirectory + "/" + contract + "/" + baseResourceName; + resourceName = getBaseContractsPath() + "/" + contract + "/" + baseResourceName; } url = Resource.getResourceUrl(ctx, resourceName); @@ -226,7 +235,7 @@ public String getBaseResourcePath() { @Override public String getBaseContractsPath() { - return webAppContractsDirectory; + return webappResourceHelper.getBaseContractsPath(); } @Override diff --git a/impl/src/main/java/com/sun/faces/application/resource/ResourceManager.java b/impl/src/main/java/com/sun/faces/application/resource/ResourceManager.java index f6ac1887dd..32e1c01ceb 100644 --- a/impl/src/main/java/com/sun/faces/application/resource/ResourceManager.java +++ b/impl/src/main/java/com/sun/faces/application/resource/ResourceManager.java @@ -16,6 +16,8 @@ package com.sun.faces.application.resource; +import static com.sun.faces.util.Util.ensureLeadingSlash; + import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -57,22 +59,25 @@ public class ResourceManager { */ private static final Pattern CONFIG_MIMETYPE_PATTERN = Pattern.compile("[a-z-]*/[a-z0-9.\\*-]*"); - private final FaceletWebappResourceHelper faceletWebappResourceHelper = new FaceletWebappResourceHelper(); - /** * {@link ResourceHelper} used for looking up webapp-based resources. */ - private final ResourceHelper webappResourceHelper = new WebappResourceHelper(); + private WebappResourceHelper webappResourceHelper = new WebappResourceHelper(); + + /** + * {@link ResourceHelper} used for looking up webapp-based facelets resources. + */ + private FaceletWebappResourceHelper faceletWebappResourceHelper = new FaceletWebappResourceHelper(webappResourceHelper); /** * {@link ResourceHelper} used for looking up classpath-based resources. */ - private final ClasspathResourceHelper classpathResourceHelper = new ClasspathResourceHelper(); + private ClasspathResourceHelper classpathResourceHelper = new ClasspathResourceHelper(); /** * Cache for storing {@link ResourceInfo} instances to reduce the cost of the resource lookups. */ - private final ResourceCache cache; + private ResourceCache cache; /** * Patterns used to find {@link ResourceInfo} instances that may have their content compressed. @@ -176,6 +181,14 @@ public Stream getViewResources(FacesContext facesContext, String path, i return faceletWebappResourceHelper.getViewResources(facesContext, path, maxDepth, options); } + public String getBaseContractsPath() { + return faceletWebappResourceHelper.getBaseContractsPath(); + } + + public boolean isContractsResource(String path) { + return ensureLeadingSlash(path).startsWith(getBaseContractsPath()); + } + // ----------------------------------------------------- Private Methods private ResourceInfo findResourceCompressed(String libraryName, String resourceName, boolean isViewResource, String localePrefix, List contracts, diff --git a/impl/src/main/java/com/sun/faces/application/view/FaceletFullStateManagementStrategy.java b/impl/src/main/java/com/sun/faces/application/view/FaceletFullStateManagementStrategy.java index 22cbc0b0fd..672b2c3009 100644 --- a/impl/src/main/java/com/sun/faces/application/view/FaceletFullStateManagementStrategy.java +++ b/impl/src/main/java/com/sun/faces/application/view/FaceletFullStateManagementStrategy.java @@ -82,7 +82,7 @@ public class FaceletFullStateManagementStrategy extends StateManagementStrategy /** * Stores the skip hint. */ - private static final String SKIP_ITERATION_HINT = "jakarta.faces.visit.SKIP_ITERATION"; + private static final Set SKIP_ITERATION_HINT = EnumSet.of(SKIP_ITERATION); /** * Stores the class map. @@ -185,41 +185,34 @@ private UIComponent locateComponentByClientId(final FacesContext context, final final List found = new ArrayList<>(); UIComponent result = null; - try { - context.getAttributes().put(SKIP_ITERATION_HINT, true); - Set hints = EnumSet.of(VisitHint.SKIP_ITERATION); - - VisitContext visitContext = VisitContext.createVisitContext(context, null, hints); - subTree.visitTree(visitContext, (visitContext1, component) -> { - VisitResult result1 = ACCEPT; - if (component.getClientId(visitContext1.getFacesContext()).equals(clientId)) { - /* - * If the client id matches up we have found our match. - */ - found.add(component); - result1 = COMPLETE; - } else if (component instanceof UIForm) { - /* - * If the component is a UIForm and it is prepending its id then we can short circuit out of here if the the client id - * of the component we are trying to find does not begin with the id of the UIForm. - */ - UIForm form = (UIForm) component; - if (form.isPrependId() && !clientId.startsWith(form.getClientId(visitContext1.getFacesContext()))) { - result1 = REJECT; - } - } else if (component instanceof NamingContainer && !clientId.startsWith(component.getClientId(visitContext1.getFacesContext()))) { - /* - * If the component is a naming container then assume it is prepending its id so if our client id we are looking for - * does not start with the naming container id we can skip visiting this tree. - */ + VisitContext visitContext = VisitContext.createVisitContext(context, null, SKIP_ITERATION_HINT); + subTree.visitTree(visitContext, (visitContext1, component) -> { + VisitResult result1 = ACCEPT; + if (component.getClientId(visitContext1.getFacesContext()).equals(clientId)) { + /* + * If the client id matches up we have found our match. + */ + found.add(component); + result1 = COMPLETE; + } else if (component instanceof UIForm) { + /* + * If the component is a UIForm and it is prepending its id then we can short circuit out of here if the the client id + * of the component we are trying to find does not begin with the id of the UIForm. + */ + UIForm form = (UIForm) component; + if (form.isPrependId() && !clientId.startsWith(form.getClientId(visitContext1.getFacesContext()))) { result1 = REJECT; } + } else if (component instanceof NamingContainer && !clientId.startsWith(component.getClientId(visitContext1.getFacesContext()))) { + /* + * If the component is a naming container then assume it is prepending its id so if our client id we are looking for + * does not start with the naming container id we can skip visiting this tree. + */ + result1 = REJECT; + } - return result1; - }); - } finally { - context.getAttributes().remove(SKIP_ITERATION_HINT); - } + return result1; + }); if (!found.isEmpty()) { result = found.get(0); @@ -311,36 +304,30 @@ private void restoreComponentState(final FacesContext context, final HashMap hints = EnumSet.of(SKIP_ITERATION); - VisitContext visitContext = VisitContext.createVisitContext(context, null, hints); + VisitContext visitContext = VisitContext.createVisitContext(context, null, SKIP_ITERATION_HINT); - viewRoot.visitTree(visitContext, (visitContext1, component) -> { - VisitResult result = ACCEPT; + viewRoot.visitTree(visitContext, (visitContext1, component) -> { + VisitResult result = ACCEPT; - String clientId = component.getClientId(context); - Object stateObj = state.get(clientId); + String clientId = component.getClientId(context); + Object stateObj = state.get(clientId); - if (stateObj != null && !stateContext.componentAddedDynamically(component)) { - boolean restoreStateNow = true; - if (stateObj instanceof StateHolderSaver) { - restoreStateNow = !((StateHolderSaver) stateObj).componentAddedDynamically(); - } - if (restoreStateNow) { - try { - component.restoreState(context, stateObj); - } catch (Exception e) { - throw new FacesException(e); - } + if (stateObj != null && !stateContext.componentAddedDynamically(component)) { + boolean restoreStateNow = true; + if (stateObj instanceof StateHolderSaver) { + restoreStateNow = !((StateHolderSaver) stateObj).componentAddedDynamically(); + } + if (restoreStateNow) { + try { + component.restoreState(context, stateObj); + } catch (Exception e) { + throw new FacesException(e); } } + } - return result; - }); - } finally { - context.getAttributes().remove(SKIP_ITERATION_HINT); - } + return result; + }); } /** @@ -592,33 +579,27 @@ private Object saveComponentState(FacesContext context) { final UIViewRoot viewRoot = context.getViewRoot(); final FacesContext finalContext = context; - context.getAttributes().put(SKIP_ITERATION_HINT, true); - Set hints = EnumSet.of(SKIP_ITERATION); - VisitContext visitContext = VisitContext.createVisitContext(context, null, hints); + VisitContext visitContext = VisitContext.createVisitContext(context, null, SKIP_ITERATION_HINT); - try { - viewRoot.visitTree(visitContext, (context1, component) -> { - VisitResult result = ACCEPT; - Object stateObj; - if (!component.isTransient()) { - if (stateContext.componentAddedDynamically(component)) { - component.getAttributes().put(DYNAMIC_COMPONENT, new Integer(getProperChildIndex(component))); - stateObj = new StateHolderSaver(finalContext, component); - } else { - stateObj = component.saveState(finalContext); - } - if (stateObj != null) { - stateMap.put(component.getClientId(finalContext), stateObj); - } + viewRoot.visitTree(visitContext, (context1, component) -> { + VisitResult result = ACCEPT; + Object stateObj; + if (!component.isTransient()) { + if (stateContext.componentAddedDynamically(component)) { + component.getAttributes().put(DYNAMIC_COMPONENT, new Integer(getProperChildIndex(component))); + stateObj = new StateHolderSaver(finalContext, component); } else { - result = REJECT; + stateObj = component.saveState(finalContext); } + if (stateObj != null) { + stateMap.put(component.getClientId(finalContext), stateObj); + } + } else { + result = REJECT; + } - return result; - }); - } finally { - context.getAttributes().remove(SKIP_ITERATION_HINT); - } + return result; + }); return stateMap; } diff --git a/impl/src/main/java/com/sun/faces/application/view/FaceletPartialStateManagementStrategy.java b/impl/src/main/java/com/sun/faces/application/view/FaceletPartialStateManagementStrategy.java index 29e763978f..6faf35945e 100644 --- a/impl/src/main/java/com/sun/faces/application/view/FaceletPartialStateManagementStrategy.java +++ b/impl/src/main/java/com/sun/faces/application/view/FaceletPartialStateManagementStrategy.java @@ -68,10 +68,16 @@ public class FaceletPartialStateManagementStrategy extends StateManagementStrate * Stores the logger. */ private static final Logger LOGGER = FacesLogger.APPLICATION_VIEW.getLogger(); + /** * Stores the skip hint. */ - private static final String SKIP_ITERATION_HINT = "jakarta.faces.visit.SKIP_ITERATION"; + private static final Set SKIP_ITERATION_HINT = EnumSet.of(SKIP_ITERATION); + + /** + * Stores the skip and lifecycle hints. + */ + private static final Set SKIP_ITERATION_AND_EXECUTE_LIFECYCLE_HINTS = EnumSet.of(VisitHint.SKIP_ITERATION, VisitHint.EXECUTE_LIFECYCLE); /** * Constructor. @@ -102,41 +108,34 @@ private UIComponent locateComponentByClientId(final FacesContext context, final final List found = new ArrayList<>(); UIComponent result = null; - try { - context.getAttributes().put(SKIP_ITERATION_HINT, true); - Set hints = EnumSet.of(SKIP_ITERATION); - - VisitContext visitContext = VisitContext.createVisitContext(context, null, hints); - subTree.visitTree(visitContext, (visitContext1, component) -> { - VisitResult result1 = ACCEPT; - if (component.getClientId(visitContext1.getFacesContext()).equals(clientId)) { - /* - * If the client id matches up we have found our match. - */ - found.add(component); - result1 = COMPLETE; - } else if (component instanceof UIForm) { - /* - * If the component is a UIForm and it is prepending its id then we can short circuit out of here if the the client id - * of the component we are trying to find does not begin with the id of the UIForm. - */ - UIForm form = (UIForm) component; - if (form.isPrependId() && !clientId.startsWith(form.getClientId(visitContext1.getFacesContext()))) { - result1 = REJECT; - } - } else if (component instanceof NamingContainer && !clientId.startsWith(component.getClientId(visitContext1.getFacesContext()))) { - /* - * If the component is a naming container then assume it is prepending its id so if our client id we are looking for - * does not start with the naming container id we can skip visiting this tree. - */ + VisitContext visitContext = VisitContext.createVisitContext(context, null, SKIP_ITERATION_HINT); + subTree.visitTree(visitContext, (visitContext1, component) -> { + VisitResult result1 = ACCEPT; + if (component.getClientId(visitContext1.getFacesContext()).equals(clientId)) { + /* + * If the client id matches up we have found our match. + */ + found.add(component); + result1 = COMPLETE; + } else if (component instanceof UIForm) { + /* + * If the component is a UIForm and it is prepending its id then we can short circuit out of here if the the client id + * of the component we are trying to find does not begin with the id of the UIForm. + */ + UIForm form = (UIForm) component; + if (form.isPrependId() && !clientId.startsWith(form.getClientId(visitContext1.getFacesContext()))) { result1 = REJECT; } + } else if (component instanceof NamingContainer && !clientId.startsWith(component.getClientId(visitContext1.getFacesContext()))) { + /* + * If the component is a naming container then assume it is prepending its id so if our client id we are looking for + * does not start with the naming container id we can skip visiting this tree. + */ + result1 = REJECT; + } - return result1; - }); - } finally { - context.getAttributes().remove(SKIP_ITERATION_HINT); - } + return result1; + }); if (!found.isEmpty()) { result = found.get(0); @@ -343,9 +342,7 @@ public UIViewRoot restoreView(FacesContext context, String viewId, String render try { stateContext.setTrackViewModifications(false); - context.getAttributes().put(SKIP_ITERATION_HINT, true); - Set hints = EnumSet.of(VisitHint.SKIP_ITERATION, VisitHint.EXECUTE_LIFECYCLE); - VisitContext visitContext = VisitContext.createVisitContext(context, null, hints); + VisitContext visitContext = VisitContext.createVisitContext(context, null, SKIP_ITERATION_AND_EXECUTE_LIFECYCLE_HINTS); viewRoot.visitTree(visitContext, (context1, target) -> { VisitResult result = VisitResult.ACCEPT; String cid = target.getClientId(context1.getFacesContext()); @@ -370,7 +367,6 @@ public UIViewRoot restoreView(FacesContext context, String viewId, String render restoreDynamicActions(context, stateContext, state); } finally { stateContext.setTrackViewModifications(true); - context.getAttributes().remove(SKIP_ITERATION_HINT); } } else { viewRoot = null; @@ -436,33 +432,27 @@ public Object saveView(FacesContext context) { final Map stateMap = new HashMap<>(); final StateContext stateContext = StateContext.getStateContext(context); - context.getAttributes().put(SKIP_ITERATION_HINT, true); - Set hints = EnumSet.of(VisitHint.SKIP_ITERATION); - VisitContext visitContext = VisitContext.createVisitContext(context, null, hints); + VisitContext visitContext = VisitContext.createVisitContext(context, null, SKIP_ITERATION_HINT); final FacesContext finalContext = context; - try { - viewRoot.visitTree(visitContext, (context1, target) -> { - VisitResult result = VisitResult.ACCEPT; - Object stateObj; - if (!target.isTransient()) { - if (stateContext.componentAddedDynamically(target)) { - target.getAttributes().put(DYNAMIC_COMPONENT, target.getParent().getChildren().indexOf(target)); - stateObj = new StateHolderSaver(finalContext, target); - } else { - stateObj = target.saveState(context1.getFacesContext()); - } - if (stateObj != null) { - stateMap.put(target.getClientId(context1.getFacesContext()), stateObj); - } + viewRoot.visitTree(visitContext, (context1, target) -> { + VisitResult result = VisitResult.ACCEPT; + Object stateObj; + if (!target.isTransient()) { + if (stateContext.componentAddedDynamically(target)) { + target.getAttributes().put(DYNAMIC_COMPONENT, target.getParent().getChildren().indexOf(target)); + stateObj = new StateHolderSaver(finalContext, target); } else { - return VisitResult.REJECT; + stateObj = target.saveState(context1.getFacesContext()); } - return result; - }); - } finally { - context.getAttributes().remove(SKIP_ITERATION_HINT); - } + if (stateObj != null) { + stateMap.put(target.getClientId(context1.getFacesContext()), stateObj); + } + } else { + return VisitResult.REJECT; + } + return result; + }); saveDynamicActions(context, stateContext, stateMap); StateContext.release(context); 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 527397871c..bd4e28529a 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,7 +43,6 @@ 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; @@ -72,6 +71,7 @@ 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; @@ -908,22 +908,25 @@ protected ResponseWriter createResponseWriter(FacesContext context) throws IOExc String encoding = (String) context.getAttributes().get(FACELETS_ENCODING_KEY); // Create a dummy ResponseWriter with a bogus writer, - // so we can figure out what content type the ReponseWriter + // so we can figure out what content type and encoding the ReponseWriter // is really going to ask for - ResponseWriter writer = renderKit.createResponseWriter(NullWriter.INSTANCE, contentType, encoding); + ResponseWriter initWriter = renderKit.createResponseWriter(NullWriter.INSTANCE, contentType, encoding); - contentType = getResponseContentType(context, writer.getContentType()); - encoding = getResponseEncoding(context, writer.getCharacterEncoding()); + contentType = getResponseContentType(context, initWriter.getContentType()); + encoding = Util.getResponseEncoding(context, Optional.ofNullable(initWriter.getCharacterEncoding())); // apply them to the response char[] buffer = new char[1028]; - HtmlUtils.writeTextForXML(writer, contentType, buffer); + HtmlUtils.writeTextForXML(initWriter, contentType, buffer); String str = String.valueOf(buffer).trim(); extContext.setResponseContentType(str); extContext.setResponseCharacterEncoding(encoding); + // Save encoding in UIViewRoot for faster consult when Util#getResponseEncoding() is invoked again elsewhere. + context.getViewRoot().getAttributes().put(FACELETS_ENCODING_KEY, encoding); + // Now, clone with the real writer - writer = writer.cloneWithWriter(extContext.getResponseOutputWriter()); + ResponseWriter writer = initWriter.cloneWithWriter(extContext.getResponseOutputWriter()); return writer; } @@ -974,58 +977,6 @@ protected void handleFaceletNotFound(FacesContext context, String viewId, String context.responseComplete(); } - /** - * @param context the {@link FacesContext} for the current request - * @param orig the original encoding - * @return the encoding to be used for this response - */ - protected String getResponseEncoding(FacesContext context, String orig) { - String encoding = orig; - - // 1. get it from request - encoding = context.getExternalContext().getRequestCharacterEncoding(); - - // 2. get it from the session - if (encoding == null) { - if (context.getExternalContext().getSession(false) != null) { - Map sessionMap = context.getExternalContext().getSessionMap(); - encoding = (String) sessionMap.get(CHARACTER_ENCODING_KEY); - if (LOGGER.isLoggable(FINEST)) { - LOGGER.log(FINEST, "Session specified alternate encoding {0}", encoding); - } - } - } - - // see if we need to override the encoding - Map ctxAttributes = context.getAttributes(); - - // 3. check the request attribute - if (ctxAttributes.containsKey(FACELETS_ENCODING_KEY)) { - encoding = (String) ctxAttributes.get(FACELETS_ENCODING_KEY); - if (LOGGER.isLoggable(FINEST)) { - LOGGER.log(FINEST, "Facelet specified alternate encoding {0}", encoding); - } - if (null != context.getExternalContext().getSession(false)) { - Map sessionMap = context.getExternalContext().getSessionMap(); - sessionMap.put(CHARACTER_ENCODING_KEY, encoding); - } - } - - // 4. default it - if (encoding == null) { - if (null != orig && 0 < orig.length()) { - encoding = orig; - } else { - encoding = "UTF-8"; - } - if (LOGGER.isLoggable(FINEST)) { - LOGGER.log(FINEST, "ResponseWriter created had a null CharacterEncoding, defaulting to {0}", orig); - } - } - - return encoding; - } - /** * @param context the {@link FacesContext} for the current request * @param orig the original contentType diff --git a/impl/src/main/java/com/sun/faces/application/view/FormOmittedChecker.java b/impl/src/main/java/com/sun/faces/application/view/FormOmittedChecker.java index 614ce02a51..2463b52c39 100644 --- a/impl/src/main/java/com/sun/faces/application/view/FormOmittedChecker.java +++ b/impl/src/main/java/com/sun/faces/application/view/FormOmittedChecker.java @@ -16,6 +16,8 @@ package com.sun.faces.application.view; +import static jakarta.faces.component.visit.VisitHint.SKIP_ITERATION; + import java.util.EnumSet; import java.util.List; import java.util.Set; @@ -42,7 +44,7 @@ class FormOmittedChecker { /** * Stores the skip hint. */ - private static final String SKIP_ITERATION_HINT = "jakarta.faces.visit.SKIP_ITERATION"; + private static final Set SKIP_ITERATION_HINT = EnumSet.of(SKIP_ITERATION); /** * Constructor. @@ -61,24 +63,17 @@ public static void check(FacesContext context) { List children = viewRoot.getChildren(); for (UIComponent child : children) { - try { - context.getAttributes().put(SKIP_ITERATION_HINT, true); - Set hints = EnumSet.of(VisitHint.SKIP_ITERATION); - - VisitContext visitContext = VisitContext.createVisitContext(context, null, hints); - child.visitTree(visitContext, (visitContext1, component) -> { - VisitResult result = VisitResult.ACCEPT; + VisitContext visitContext = VisitContext.createVisitContext(context, null, SKIP_ITERATION_HINT); + child.visitTree(visitContext, (visitContext1, component) -> { + VisitResult result = VisitResult.ACCEPT; - if (isForm(component)) { - result = VisitResult.REJECT; - } else if (isInNeedOfForm(component)) { - addFormOmittedMessage(finalContext, component); - } - return result; - }); - } finally { - context.getAttributes().remove(SKIP_ITERATION_HINT); - } + if (isForm(component)) { + result = VisitResult.REJECT; + } else if (isInNeedOfForm(component)) { + addFormOmittedMessage(finalContext, component); + } + return result; + }); } } diff --git a/impl/src/main/java/com/sun/faces/application/view/MultiViewHandler.java b/impl/src/main/java/com/sun/faces/application/view/MultiViewHandler.java index 9fa2bcca7f..51b6c1e9e5 100644 --- a/impl/src/main/java/com/sun/faces/application/view/MultiViewHandler.java +++ b/impl/src/main/java/com/sun/faces/application/view/MultiViewHandler.java @@ -17,7 +17,6 @@ package com.sun.faces.application.view; -import static com.sun.faces.RIConstants.FACELETS_ENCODING_KEY; import static com.sun.faces.RIConstants.SAVESTATE_FIELD_MARKER; import static com.sun.faces.renderkit.RenderKitUtils.PredefinedPostbackParameter.RENDER_KIT_ID_PARAM; import static com.sun.faces.renderkit.RenderKitUtils.getResponseStateManager; @@ -35,10 +34,8 @@ import static jakarta.servlet.http.MappingMatch.EXACT; import static jakarta.servlet.http.MappingMatch.EXTENSION; import static jakarta.servlet.http.MappingMatch.PATH; -import static java.text.MessageFormat.format; import static java.util.Collections.unmodifiableSet; import static java.util.Objects.requireNonNull; -import static java.util.logging.Level.FINE; import static java.util.logging.Level.SEVERE; import static java.util.logging.Level.WARNING; @@ -350,24 +347,7 @@ public boolean removeProtectedView(String urlPattern) { */ @Override public String getRedirectURL(FacesContext context, String viewId, Map> parameters, boolean includeViewParams) { - String encodingFromContext = (String) context.getAttributes().get(FACELETS_ENCODING_KEY); - if (encodingFromContext == null) { - encodingFromContext = (String) context.getViewRoot().getAttributes().get(FACELETS_ENCODING_KEY); - } - - String responseEncoding; - - if (encodingFromContext == null) { - try { - responseEncoding = context.getExternalContext().getResponseCharacterEncoding(); - } catch (Exception e) { - LOGGER.log(FINE, e, () -> - format("Unable to obtain response character encoding from ExternalContext {0}. Using UTF-8.", context.getExternalContext())); - responseEncoding = "UTF-8"; - } - } else { - responseEncoding = encodingFromContext; - } + String responseEncoding = Util.getResponseEncoding(context); if (parameters != null) { Map> decodedParameters = new HashMap<>(); diff --git a/impl/src/main/java/com/sun/faces/application/view/ViewScopeContextManager.java b/impl/src/main/java/com/sun/faces/application/view/ViewScopeContextManager.java index 1dee095be2..ccad44342d 100644 --- a/impl/src/main/java/com/sun/faces/application/view/ViewScopeContextManager.java +++ b/impl/src/main/java/com/sun/faces/application/view/ViewScopeContextManager.java @@ -20,6 +20,7 @@ import static com.sun.faces.application.view.ViewScopeManager.VIEW_MAP_ID; import static com.sun.faces.cdi.CdiUtils.getBeanReference; import static com.sun.faces.config.WebConfiguration.BooleanWebContextInitParameter.EnableDistributable; +import static com.sun.faces.context.SessionMap.getMutex; import static com.sun.faces.util.Util.getCdiBeanManager; import static java.util.logging.Level.FINEST; import static java.util.logging.Level.WARNING; @@ -95,7 +96,7 @@ public void clear(FacesContext facesContext, Map viewMap) { LOGGER.log(WARNING, "Cannot locate the view map to clear in the active maps: {0}", viewMap); } } - + /** * Clear the given view map. * @@ -247,7 +248,7 @@ private Map getContextMap(FacesContext facesCont String viewMapId = (String) facesContext.getViewRoot().getTransientStateHelper().getTransient(VIEW_MAP_ID); if (activeViewScopeContexts == null && create) { - synchronized (session) { + synchronized (getMutex(session)) { activeViewScopeContexts = new ConcurrentHashMap<>(); sessionMap.put(ACTIVE_VIEW_CONTEXTS, activeViewScopeContexts); } @@ -332,7 +333,7 @@ public void sessionDestroyed(HttpSessionEvent httpSessionEvent) { HttpSession session = httpSessionEvent.getSession(); - Map> activeViewScopeContexts = (Map>) + Map> activeViewScopeContexts = (Map>) session.getAttribute(ACTIVE_VIEW_CONTEXTS); if (activeViewScopeContexts != null) { Map activeViewMaps = (Map) session.getAttribute(ViewScopeManager.ACTIVE_VIEW_MAPS); diff --git a/impl/src/main/java/com/sun/faces/cdi/clientwindow/ClientWindowScopeContextManager.java b/impl/src/main/java/com/sun/faces/cdi/clientwindow/ClientWindowScopeContextManager.java index 7ca248bad7..6f6d234681 100644 --- a/impl/src/main/java/com/sun/faces/cdi/clientwindow/ClientWindowScopeContextManager.java +++ b/impl/src/main/java/com/sun/faces/cdi/clientwindow/ClientWindowScopeContextManager.java @@ -18,11 +18,14 @@ package com.sun.faces.cdi.clientwindow; import static com.sun.faces.config.WebConfiguration.BooleanWebContextInitParameter.EnableDistributable; +import static com.sun.faces.config.WebConfiguration.WebContextInitParameter.NumberOfClientWindows; +import static com.sun.faces.context.SessionMap.getMutex; import static java.util.logging.Level.FINEST; import java.util.Collections; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; import java.util.logging.Logger; import com.sun.faces.config.WebConfiguration; @@ -34,7 +37,6 @@ import jakarta.enterprise.inject.spi.PassivationCapable; import jakarta.faces.context.ExternalContext; import jakarta.faces.context.FacesContext; -import jakarta.faces.lifecycle.ClientWindow; import jakarta.servlet.http.HttpSession; import jakarta.servlet.http.HttpSessionEvent; @@ -142,12 +144,22 @@ private Map getContextMap(FacesContext f String clientWindowId = getCurrentClientWindowId(facesContext); if (clientWindowScopeContexts == null && create) { - synchronized (session) { - Integer size = (Integer) sessionMap.get(ClientWindow.NUMBER_OF_CLIENT_WINDOWS_PARAM_NAME); - if (size == null) { - size = 10; + Integer numberOfClientWindows = null; + + try { + numberOfClientWindows = Integer.parseInt(WebConfiguration.getInstance(externalContext).getOptionValue(NumberOfClientWindows)); + } catch (NumberFormatException nfe) { + if (LOGGER.isLoggable(Level.WARNING)) { + LOGGER.log(Level.WARNING, "Unable to set number of client windows. Defaulting to {0}", NumberOfClientWindows.getDefaultValue()); } - sessionMap.put(CLIENT_WINDOW_CONTEXTS, Collections.synchronizedMap(new LRUMap(size))); + } + + if (numberOfClientWindows == null) { + numberOfClientWindows = Integer.valueOf(NumberOfClientWindows.getDefaultValue()); + } + + synchronized (getMutex(session)) { + sessionMap.put(CLIENT_WINDOW_CONTEXTS, Collections.synchronizedMap(new LRUMap(numberOfClientWindows))); } } diff --git a/impl/src/main/java/com/sun/faces/config/ConfigureListener.java b/impl/src/main/java/com/sun/faces/config/ConfigureListener.java index fbe1f9a190..bb0270351d 100644 --- a/impl/src/main/java/com/sun/faces/config/ConfigureListener.java +++ b/impl/src/main/java/com/sun/faces/config/ConfigureListener.java @@ -27,6 +27,8 @@ import static com.sun.faces.config.WebConfiguration.BooleanWebContextInitParameter.ForceLoadFacesConfigFiles; import static com.sun.faces.config.WebConfiguration.BooleanWebContextInitParameter.VerifyFacesConfigObjects; import static com.sun.faces.config.WebConfiguration.WebContextInitParameter.JakartaFacesProjectStage; +import static com.sun.faces.context.SessionMap.createMutex; +import static com.sun.faces.context.SessionMap.removeMutex; import static com.sun.faces.push.WebsocketEndpoint.URI_TEMPLATE; import static java.lang.Boolean.TRUE; import static java.text.MessageFormat.format; @@ -42,6 +44,7 @@ import java.net.URI; import java.net.URL; import java.net.URLConnection; +import java.text.MessageFormat; import java.util.ArrayList; import java.util.Collection; import java.util.Enumeration; @@ -121,6 +124,7 @@ public void contextInitialized(ServletContextEvent servletContextEvent) { } InitFacesContext initFacesContext = new InitFacesContext(servletContext); + Util.getCdiBeanManager(initFacesContext); // #5232 Fail fast when CDI is really not available. LOGGER.log(FINE, () -> format("ConfigureListener.contextInitialized({0})", servletContext.getContextPath())); @@ -149,8 +153,11 @@ public void contextInitialized(ServletContextEvent servletContextEvent) { } } - if (webXmlProcessor.isDistributablePresent()) { - webConfig.setOptionEnabled(WebConfiguration.BooleanWebContextInitParameter.EnableDistributable, true); + // Do not override if already defined + if (!webConfig.isSet(WebConfiguration.BooleanWebContextInitParameter.EnableDistributable)) { + webConfig.setOptionEnabled(WebConfiguration.BooleanWebContextInitParameter.EnableDistributable, webXmlProcessor.isDistributablePresent()); + } + if (webConfig.isOptionEnabled(WebConfiguration.BooleanWebContextInitParameter.EnableDistributable)) { servletContext.setAttribute(WebConfiguration.BooleanWebContextInitParameter.EnableDistributable.getQualifiedName(), TRUE); } @@ -162,7 +169,7 @@ public void contextInitialized(ServletContextEvent servletContextEvent) { try { if (LOGGER.isLoggable(INFO)) { - LOGGER.log(INFO, "faces.config.listener.version", servletContext.getContextPath()); + LOGGER.log(INFO, MessageFormat.format("Initializing Mojarra {0} for context {1}", "5.0.3_pizzi" , servletContext.getContextPath()) ); } if (webConfig.isOptionEnabled(VerifyFacesConfigObjects)) { @@ -346,6 +353,8 @@ public void requestInitialized(ServletRequestEvent event) { @Override public void sessionCreated(HttpSessionEvent event) { + createMutex(event.getSession()); + if (webAppListener != null) { webAppListener.sessionCreated(event); } @@ -353,6 +362,8 @@ public void sessionCreated(HttpSessionEvent event) { @Override public void sessionDestroyed(HttpSessionEvent event) { + removeMutex(event.getSession()); + if (webAppListener != null) { webAppListener.sessionDestroyed(event); } diff --git a/impl/src/main/java/com/sun/faces/config/FacesInitializer.java b/impl/src/main/java/com/sun/faces/config/FacesInitializer.java index e56a2bc78d..c01c0aa277 100644 --- a/impl/src/main/java/com/sun/faces/config/FacesInitializer.java +++ b/impl/src/main/java/com/sun/faces/config/FacesInitializer.java @@ -22,11 +22,16 @@ import static com.sun.faces.RIConstants.FACES_SERVLET_REGISTRATION; import static com.sun.faces.util.Util.isEmpty; import static java.lang.Boolean.parseBoolean; +import static java.util.logging.Level.WARNING; import java.util.HashSet; import java.util.Map; import java.util.Set; +import java.util.logging.Logger; +import com.sun.faces.util.FacesLogger; + +import jakarta.enterprise.inject.spi.BeanManager; import jakarta.faces.annotation.FacesConfig; import jakarta.faces.application.ResourceDependencies; import jakarta.faces.application.ResourceDependency; @@ -84,6 +89,8 @@ public class FacesInitializer implements ServletContainerInitializer { // NOTE: Logging should not be used with this class. + // NOTE: It can, we only need to ensure that the logger is only invoked when there's a (Init)FacesContext. + private static final Logger LOGGER = FacesLogger.CONFIG.getLogger(); public static final String FACES_PACKAGE_PREFIX = "jakarta.faces."; public static final String MOJARRA_PACKAGE_PREFIX = "com.sun.faces."; @@ -112,6 +119,7 @@ public void onStartup(Set> classes, ServletContext servletContext) thro } // Other concerns also handled if there is an existing Faces Servlet mapping + handleCdiConcerns(servletContext); handleWebSocketConcerns(servletContext); // The Configure listener will do the bulk of initializing (configuring) Faces in a later phase. @@ -204,6 +212,41 @@ private static void handleMappingConcerns(ServletContext servletContext) throws servletContext.setAttribute(FACES_SERVLET_REGISTRATION, newFacesServletRegistration); } + private static void handleCdiConcerns(ServletContext context) throws ServletException { + // If Weld is used as CDI impl then make sure it doesn't skip initialization when there's no BDA. See also #5232 and #5321 + + if (context.getAttribute("org.jboss.weld.environment.servlet.jakarta.enterprise.inject.spi.BeanManager") instanceof BeanManager) { + // Already initialized. + return; + } + + ClassLoader cl = context.getClassLoader(); + Class weldInitializerClass; + + try { + weldInitializerClass = cl.loadClass("org.jboss.weld.environment.servlet.EnhancedListener"); + } + catch (ClassNotFoundException ignore) { + // Weld is actually not being used. That's OK for now, so just continue as usual. + return; + } + + // Weld is being used so let's make sure it doesn't skip initialization when there's no BDA. + context.setInitParameter("org.jboss.weld.environment.servlet.archive.isolation", "false"); + + if (context.getAttribute("org.jboss.weld.environment.servlet.enhancedListenerUsed") == Boolean.TRUE) { + try { + LOGGER.log(WARNING, "Weld skipped initialization - forcing it to reinitialize"); + ServletContainerInitializer weldInitializer = (ServletContainerInitializer) weldInitializerClass.getConstructor().newInstance(); + weldInitializer.onStartup(null, context); + } + catch (Exception | LinkageError e) { + // Weld is being used but it failed for unclear reason while CDI should be enabled. That's not OK, so rethrow as ServletException. + throw new ServletException("Reinitializing Weld failed - giving up, please make sure your project contains at least one bean class with a bean defining annotation and retry", e); + } + } + } + private static void handleWebSocketConcerns(ServletContext context) throws ServletException { if (context.getAttribute(ServerContainer.class.getName()) != null) { // Already initialized diff --git a/impl/src/main/java/com/sun/faces/config/InitFacesContext.java b/impl/src/main/java/com/sun/faces/config/InitFacesContext.java index d2f88c00c5..6adf608666 100644 --- a/impl/src/main/java/com/sun/faces/config/InitFacesContext.java +++ b/impl/src/main/java/com/sun/faces/config/InitFacesContext.java @@ -32,6 +32,7 @@ import com.sun.faces.config.initfacescontext.NoOpFacesContext; import com.sun.faces.config.initfacescontext.ServletContextAdapter; import com.sun.faces.context.ApplicationMap; +import com.sun.faces.context.ExceptionHandlerImpl; import com.sun.faces.util.FacesLogger; import com.sun.faces.util.Util; @@ -41,6 +42,7 @@ import jakarta.faces.application.ApplicationFactory; import jakarta.faces.application.ProjectStage; import jakarta.faces.component.UIViewRoot; +import jakarta.faces.context.ExceptionHandler; import jakarta.faces.context.ExternalContext; import jakarta.faces.context.FacesContext; import jakarta.servlet.ServletContext; @@ -109,6 +111,11 @@ public Application getApplication() { return factory.getApplication(); } + @Override + public ExceptionHandler getExceptionHandler() { + return new ExceptionHandlerImpl(false); + } + @Override public boolean isProjectStage(ProjectStage stage) { if (stage == null) { diff --git a/impl/src/main/java/com/sun/faces/config/WebConfiguration.java b/impl/src/main/java/com/sun/faces/config/WebConfiguration.java index 24102bd487..28c3aa4c65 100644 --- a/impl/src/main/java/com/sun/faces/config/WebConfiguration.java +++ b/impl/src/main/java/com/sun/faces/config/WebConfiguration.java @@ -396,7 +396,8 @@ private void discoverResourceLibraryContracts() { Set candidates; // Scan for "contractMappings" in the web app root - String contractsDirName = getOptionValue(WebContextInitParameter.WebAppContractsDirectory); + ApplicationAssociate associate = ApplicationAssociate.getCurrentInstance(); + String contractsDirName = associate.getResourceManager().getBaseContractsPath(); assert null != contractsDirName; candidates = extContex.getResourcePaths(contractsDirName); if (null != candidates) { @@ -437,7 +438,6 @@ private void discoverResourceLibraryContracts() { Map> contractMappings = new HashMap<>(); - ApplicationAssociate associate = ApplicationAssociate.getCurrentInstance(); Map> contractsFromConfig = associate.getResourceLibraryContracts(); List contractsToExpose; @@ -748,13 +748,13 @@ public enum WebContextInitParameter { // if a parameter is to be deprecated, then the Deprecated enum element *must* appear after the one that is taking // its place. The reporting logic depends on this. - ManagedBeanFactoryDecorator("com.sun.faces.managedBeanFactoryDecoratorClass", ""), StateSavingMethod(StateManager.STATE_SAVING_METHOD_PARAM_NAME, "server"), FaceletsSuffix(ViewHandler.FACELETS_SUFFIX_PARAM_NAME, ViewHandler.DEFAULT_FACELETS_SUFFIX), JakartaFacesConfigFiles(FacesServlet.CONFIG_FILES_ATTR, ""), JakartaFacesProjectStage(ProjectStage.PROJECT_STAGE_PARAM_NAME, "Production"), AlternateLifecycleId(FacesServlet.LIFECYCLE_ID_ATTR, ""), ResourceExcludes(ResourceHandler.RESOURCE_EXCLUDES_PARAM_NAME, ResourceHandler.RESOURCE_EXCLUDES_DEFAULT_VALUE), + NumberOfClientWindows(ClientWindow.NUMBER_OF_CLIENT_WINDOWS_PARAM_NAME, "10"), NumberOfViews("com.sun.faces.numberOfViewsInSession", "15"), NumberOfLogicalViews("com.sun.faces.numberOfLogicalViews", "15"), NumberOfActiveViewMaps("com.sun.faces.numberOfActiveViewMaps", "25"), @@ -765,7 +765,6 @@ public enum WebContextInitParameter { FaceletsBufferSize(ViewHandler.FACELETS_BUFFER_SIZE_PARAM_NAME, "1024"), ClientStateWriteBufferSize("com.sun.faces.clientStateWriteBufferSize", "8192"), ResourceBufferSize("com.sun.faces.resourceBufferSize", "2048"), - ExpressionFactory("com.sun.faces.expressionFactory", "com.sun.el.ExpressionFactoryImpl"), ClientStateTimeout("com.sun.faces.clientStateTimeout", ""), DefaultResourceMaxAge("com.sun.faces.defaultResourceMaxAge", "604800000"), // 7 days ResourceUpdateCheckPeriod("com.sun.faces.resourceUpdateCheckPeriod", "5"), // in minutes @@ -857,16 +856,12 @@ public enum BooleanWebContextInitParameter { DisableFacesServletAutomaticMapping(FacesServlet.DISABLE_FACESSERVLET_TO_XHTML_PARAM_NAME, false), AutomaticExtensionlessMapping(FacesServlet.AUTOMATIC_EXTENSIONLESS_MAPPING_PARAM_NAME, false), EnableClientStateDebugging("com.sun.faces.enableClientStateDebugging", false), - EnableHtmlTagLibraryValidator("com.sun.faces.enableHtmlTagLibValidator", false), - EnableCoreTagLibraryValidator("com.sun.faces.enableCoreTagLibValidator", false), PreferXHTMLContentType("com.sun.faces.preferXHTML", false), CompressViewState("com.sun.faces.compressViewState", true), - CompressJavaScript("com.sun.faces.compressJavaScript", true), EnableJSStyleHiding("com.sun.faces.enableJSStyleHiding", false), EnableScriptInAttributeValue("com.sun.faces.enableScriptsInAttributeValues", true), WriteStateAtFormEnd("com.sun.faces.writeStateAtFormEnd", true), EnableLazyBeanValidation("com.sun.faces.enableLazyBeanValidation", true), - EnableLoadBundle11Compatibility("com.sun.faces.enabledLoadBundle11Compatibility", false), SerializeServerState(StateManager.SERIALIZE_SERVER_STATE_PARAM_NAME, false), EnableViewStateIdRendering("com.sun.faces.enableViewStateIdRendering", true), RegisterConverterPropertyEditors("com.sun.faces.registerConverterPropertyEditors", false), @@ -881,9 +876,7 @@ public enum BooleanWebContextInitParameter { EnableThreading("com.sun.faces.enableThreading", false), AllowTextChildren("com.sun.faces.allowTextChildren", false), CacheResourceModificationTimestamp("com.sun.faces.cacheResourceModificationTimestamp", false), - EnableAgressiveSessionDirtying("com.sun.faces.enableAgressiveSessionDirtying", false), EnableDistributable("com.sun.faces.enableDistributable", false), - EnableFaceletsResourceResolverResolveCompositeComponents("com.sun.faces.enableFaceletsResourceResolverCompositeComponents", false), EnableMissingResourceLibraryDetection("com.sun.faces.enableMissingResourceLibraryDetection", false), DisableIdUniquenessCheck("com.sun.faces.disableIdUniquenessCheck", false), EnableTransitionTimeNoOpFlash("com.sun.faces.enableTransitionTimeNoOpFlash", false), diff --git a/impl/src/main/java/com/sun/faces/config/manager/Documents.java b/impl/src/main/java/com/sun/faces/config/manager/Documents.java index e90fd44df6..218445a90e 100644 --- a/impl/src/main/java/com/sun/faces/config/manager/Documents.java +++ b/impl/src/main/java/com/sun/faces/config/manager/Documents.java @@ -18,6 +18,7 @@ import static com.sun.faces.RIConstants.DOCUMENT_NAMESPACE; import static com.sun.faces.RIConstants.DOCUMENT_VERSION; +import static com.sun.faces.util.Util.createLocalDocumentBuilderFactory; import static com.sun.faces.util.Util.isEmpty; import static java.util.Arrays.asList; import static java.util.logging.Level.INFO; @@ -231,7 +232,7 @@ public static DocumentInfo[] sortDocuments(DocumentInfo[] facesDocuments, FacesC } private static DOMImplementation createDOMImplementation() throws ParserConfigurationException { - DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); + DocumentBuilderFactory documentBuilderFactory = createLocalDocumentBuilderFactory(); documentBuilderFactory.setNamespaceAware(true); return documentBuilderFactory.newDocumentBuilder().getDOMImplementation(); diff --git a/impl/src/main/java/com/sun/faces/config/processor/FacesFlowDefinitionConfigProcessor.java b/impl/src/main/java/com/sun/faces/config/processor/FacesFlowDefinitionConfigProcessor.java index ff7308dad6..294e737125 100644 --- a/impl/src/main/java/com/sun/faces/config/processor/FacesFlowDefinitionConfigProcessor.java +++ b/impl/src/main/java/com/sun/faces/config/processor/FacesFlowDefinitionConfigProcessor.java @@ -16,6 +16,7 @@ package com.sun.faces.config.processor; +import static com.sun.faces.util.Util.createLocalDocumentBuilderFactory; import static com.sun.faces.util.Util.notNull; import java.net.MalformedURLException; @@ -127,7 +128,7 @@ public static Document synthesizeEmptyFlowDefinition(URI uri) throws ParserConfi } String flowName = segments[segments.length - 2]; - DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + DocumentBuilderFactory dbf = createLocalDocumentBuilderFactory(); dbf.setNamespaceAware(true); DocumentBuilder builder = dbf.newDocumentBuilder(); DOMImplementation domImpl = builder.getDOMImplementation(); diff --git a/impl/src/main/java/com/sun/faces/context/ExternalContextImpl.java b/impl/src/main/java/com/sun/faces/context/ExternalContextImpl.java index 55dbf6c5d8..227335764a 100644 --- a/impl/src/main/java/com/sun/faces/context/ExternalContextImpl.java +++ b/impl/src/main/java/com/sun/faces/context/ExternalContextImpl.java @@ -638,9 +638,8 @@ public void redirect(String requestURI) throws IOException { pwriter = ctx.getPartialViewContext().getPartialResponseWriter(); } setResponseContentType(RIConstants.TEXT_XML_CONTENT_TYPE); - setResponseCharacterEncoding("UTF-8"); + setResponseCharacterEncoding(RIConstants.CHAR_ENCODING); addResponseHeader("Cache-Control", "no-cache"); -// pwriter.writePreamble("\n"); pwriter.startDocument(); pwriter.redirect(requestURI); pwriter.endDocument(); @@ -964,14 +963,7 @@ public boolean isSecure() { @Override public String encodeBookmarkableURL(String baseUrl, Map> parameters) { - FacesContext context = FacesContext.getCurrentInstance(); - String encodingFromContext = (String) context.getAttributes().get(RIConstants.FACELETS_ENCODING_KEY); - if (null == encodingFromContext) { - encodingFromContext = (String) context.getViewRoot().getAttributes().get(RIConstants.FACELETS_ENCODING_KEY); - } - - String currentResponseEncoding = null != encodingFromContext ? encodingFromContext : getResponseCharacterEncoding(); - + String currentResponseEncoding = Util.getResponseEncoding(FacesContext.getCurrentInstance()); UrlBuilder builder = new UrlBuilder(baseUrl, currentResponseEncoding); builder.addParameters(parameters); return builder.createUrl(); @@ -980,14 +972,7 @@ public String encodeBookmarkableURL(String baseUrl, Map> pa @Override public String encodeRedirectURL(String baseUrl, Map> parameters) { - FacesContext context = FacesContext.getCurrentInstance(); - String encodingFromContext = (String) context.getAttributes().get(RIConstants.FACELETS_ENCODING_KEY); - if (null == encodingFromContext) { - encodingFromContext = (String) context.getViewRoot().getAttributes().get(RIConstants.FACELETS_ENCODING_KEY); - } - - String currentResponseEncoding = null != encodingFromContext ? encodingFromContext : getResponseCharacterEncoding(); - + String currentResponseEncoding = Util.getResponseEncoding(FacesContext.getCurrentInstance()); UrlBuilder builder = new UrlBuilder(baseUrl, currentResponseEncoding); builder.addParameters(parameters); return builder.createUrl(); @@ -1003,14 +988,7 @@ public String encodePartialActionURL(String url) { String message = MessageUtils.getExceptionMessageString(MessageUtils.NULL_PARAMETERS_ERROR_MESSAGE_ID, "url"); throw new NullPointerException(message); } - FacesContext context = FacesContext.getCurrentInstance(); - String encodingFromContext = (String) context.getAttributes().get(RIConstants.FACELETS_ENCODING_KEY); - if (null == encodingFromContext) { - encodingFromContext = (String) context.getViewRoot().getAttributes().get(RIConstants.FACELETS_ENCODING_KEY); - } - - String currentResponseEncoding = null != encodingFromContext ? encodingFromContext : getResponseCharacterEncoding(); - + String currentResponseEncoding = Util.getResponseEncoding(FacesContext.getCurrentInstance()); UrlBuilder builder = new UrlBuilder(url, currentResponseEncoding); return ((HttpServletResponse) response).encodeURL(builder.createUrl()); } @@ -1068,7 +1046,7 @@ private void pushIfPossibleAndNecessary(String result) { private PushBuilder getPushBuilder(FacesContext context) { ExternalContext extContext = context.getExternalContext(); - + if (extContext.getRequest() instanceof HttpServletRequest) { HttpServletRequest hreq = (HttpServletRequest) extContext.getRequest(); diff --git a/impl/src/main/java/com/sun/faces/context/FacesContextImpl.java b/impl/src/main/java/com/sun/faces/context/FacesContextImpl.java index c58e997c3e..af05a58d83 100644 --- a/impl/src/main/java/com/sun/faces/context/FacesContextImpl.java +++ b/impl/src/main/java/com/sun/faces/context/FacesContextImpl.java @@ -16,6 +16,8 @@ package com.sun.faces.context; +import static java.util.stream.Collectors.toSet; + import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -24,9 +26,11 @@ import java.util.List; import java.util.Map; import java.util.NoSuchElementException; +import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; +import com.sun.faces.cdi.CdiExtension; import com.sun.faces.el.ELContextImpl; import com.sun.faces.renderkit.RenderKitUtils; import com.sun.faces.util.FacesLogger; @@ -548,7 +552,8 @@ public void release() { DEFAULT_FACES_CONTEXT.remove(); // Destroy our instance produced by FacesContextProducer. - Bean bean = beanManager.resolve(beanManager.getBeans(FacesContext.class)); + Set> beans = beanManager.getBeans(FacesContext.class).stream().filter(bean -> CdiExtension.class.isAssignableFrom(bean.getBeanClass())).collect(toSet()); + Bean bean = beanManager.resolve(beans); ((AlterableContext) beanManager.getContext(bean.getScope())).destroy(bean); } diff --git a/impl/src/main/java/com/sun/faces/context/PartialViewContextImpl.java b/impl/src/main/java/com/sun/faces/context/PartialViewContextImpl.java index 5f15515c99..d267666e99 100644 --- a/impl/src/main/java/com/sun/faces/context/PartialViewContextImpl.java +++ b/impl/src/main/java/com/sun/faces/context/PartialViewContextImpl.java @@ -281,11 +281,6 @@ public void processPartial(PhaseId phaseId) { exContext.setResponseContentType(RIConstants.TEXT_XML_CONTENT_TYPE); exContext.addResponseHeader("Cache-Control", "no-cache"); -// String encoding = writer.getCharacterEncoding( ); -// if( encoding == null ) { -// encoding = "UTF-8"; -// } -// writer.writePreamble("\n"); writer.startDocument(); if (isResetValues()) { @@ -322,7 +317,7 @@ public void processPartial(PhaseId phaseId) { } } } - + private void doFlashPostPhaseActions(FacesContext ctx) { try { ctx.getExternalContext().getFlash().doPostPhaseActions(ctx); @@ -630,7 +625,7 @@ public DelayedInitPartialResponseWriter(PartialViewContextImpl ctx) { super(null); this.ctx = ctx; ExternalContext extCtx = ctx.ctx.getExternalContext(); - + if (extCtx.isResponseCommitted()) { LOGGER.log(WARNING, "Response is already committed - cannot reconfigure it anymore"); } diff --git a/impl/src/main/java/com/sun/faces/context/SessionMap.java b/impl/src/main/java/com/sun/faces/context/SessionMap.java index 29a5972a86..54ea23ec4a 100644 --- a/impl/src/main/java/com/sun/faces/context/SessionMap.java +++ b/impl/src/main/java/com/sun/faces/context/SessionMap.java @@ -16,6 +16,8 @@ package com.sun.faces.context; +import static java.util.Optional.ofNullable; + import java.io.Serializable; import java.util.Collections; import java.util.Enumeration; @@ -37,6 +39,7 @@ public class SessionMap extends BaseContextMap { private static final Logger LOGGER = FacesLogger.APPLICATION.getLogger(); + private static final String MUTEX = Mutex.class.getName(); private final HttpServletRequest request; private final ProjectStage stage; @@ -186,4 +189,22 @@ protected HttpSession getSession(boolean createNew) { return request.getSession(createNew); } + // ----------------------------------------------------------- Session Mutex + + private static final class Mutex implements Serializable { + private static final long serialVersionUID = 1L; + } + + public static void createMutex(HttpSession session) { + session.setAttribute(MUTEX, new Mutex()); + } + + public static Object getMutex(Object session) { + return session instanceof HttpSession ? ofNullable(((HttpSession) session).getAttribute(MUTEX)).orElse(session) : session; + } + + public static void removeMutex(HttpSession session) { + session.removeAttribute(MUTEX); + } + } // END SessionMap diff --git a/impl/src/main/java/com/sun/faces/context/UrlBuilder.java b/impl/src/main/java/com/sun/faces/context/UrlBuilder.java index c4f34fb657..ffa48405bb 100644 --- a/impl/src/main/java/com/sun/faces/context/UrlBuilder.java +++ b/impl/src/main/java/com/sun/faces/context/UrlBuilder.java @@ -16,6 +16,9 @@ package com.sun.faces.context; +import static com.sun.faces.RIConstants.CHAR_ENCODING; + +import java.io.UnsupportedEncodingException; import static java.nio.charset.StandardCharsets.UTF_8; import java.net.URLEncoder; @@ -75,7 +78,7 @@ public UrlBuilder(String url, String encoding) { } public UrlBuilder(String url) { - this(url, DEFAULT_ENCODING); + this(url, CHAR_ENCODING); } // ---------------------------------------------------------- Public Methods diff --git a/impl/src/main/java/com/sun/faces/context/flash/ELFlash.java b/impl/src/main/java/com/sun/faces/context/flash/ELFlash.java index a85cf21975..aedfff14bb 100644 --- a/impl/src/main/java/com/sun/faces/context/flash/ELFlash.java +++ b/impl/src/main/java/com/sun/faces/context/flash/ELFlash.java @@ -18,8 +18,8 @@ import static com.sun.faces.config.WebConfiguration.BooleanWebContextInitParameter.EnableDistributable; import static com.sun.faces.config.WebConfiguration.BooleanWebContextInitParameter.ForceAlwaysWriteFlashCookie; +import static java.nio.charset.StandardCharsets.UTF_8; -import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.net.URLEncoder; import java.security.InvalidKeyException; @@ -640,7 +640,7 @@ public void doPostPhaseActions(FacesContext context) { * necessary because the call to extContext.flushBuffer() is too late, the response has already been committed by that * point. outgoingResponseIsRedirect is false. *

- * + * * @param context the involved faces context * @param outgoingResponseIsRedirect whether outgoing response is redirect */ @@ -1313,15 +1313,7 @@ void expireNext_MovePreviousToNext() { void decode(FacesContext context, ELFlash flash, Cookie cookie) throws InvalidKeyException { String temp; String value; - - String urlDecodedValue = null; - - try { - urlDecodedValue = URLDecoder.decode(cookie.getValue(), "UTF-8"); - } catch (UnsupportedEncodingException uee) { - urlDecodedValue = cookie.getValue(); - } - + String urlDecodedValue = URLDecoder.decode(cookie.getValue(), UTF_8); value = guard.decrypt(urlDecodedValue); try { @@ -1390,16 +1382,10 @@ void decode(FacesContext context, ELFlash flash, Cookie cookie) throws InvalidKe *

*/ Cookie encode() { - Cookie result = null; - String value = (null != previousRequestFlashInfo ? previousRequestFlashInfo.encode() : "") + "_" + (null != nextRequestFlashInfo ? nextRequestFlashInfo.encode() : ""); String encryptedValue = guard.encrypt(value); - try { - result = new Cookie(FLASH_COOKIE_NAME, URLEncoder.encode(encryptedValue, "UTF-8")); - } catch (UnsupportedEncodingException uee) { - result = new Cookie(FLASH_COOKIE_NAME, encryptedValue); - } + Cookie result = new Cookie(FLASH_COOKIE_NAME, URLEncoder.encode(encryptedValue, UTF_8)); if (1 == value.length()) { result.setMaxAge(0); diff --git a/impl/src/main/java/com/sun/faces/facelets/component/UIRepeat.java b/impl/src/main/java/com/sun/faces/facelets/component/UIRepeat.java index 217d790f7a..99f5456749 100644 --- a/impl/src/main/java/com/sun/faces/facelets/component/UIRepeat.java +++ b/impl/src/main/java/com/sun/faces/facelets/component/UIRepeat.java @@ -938,13 +938,16 @@ public void broadcast(FacesEvent event) throws AbortProcessingException { FacesEvent target = idxEvent.getTarget(); FacesContext ctx = target.getFacesContext(); resetDataModel(ctx); - int prevIndex = index; + int idx = idxEvent.getIndex(); + int prevIndex = this.index; + boolean needsToSetIndex = idx != -1 || prevIndex != -1; // #5213 UIComponent source = target.getComponent(); UIComponent compositeParent = null; try { int rowCount = getDataModel().getRowCount(); - int idx = idxEvent.getIndex(); - setIndex(ctx, idx); + if (needsToSetIndex) { + setIndex(ctx, idx); + } Integer begin = getBegin(); Integer end = getEnd(); Integer step = getStep(); @@ -969,7 +972,9 @@ public void broadcast(FacesEvent event) throws AbortProcessingException { compositeParent.popComponentFromEL(ctx); } updateIterationStatus(ctx, null); - setIndex(ctx, prevIndex); + if (needsToSetIndex) { + setIndex(ctx, prevIndex); + } } } else { super.broadcast(event); diff --git a/impl/src/main/java/com/sun/faces/facelets/impl/DefaultFacelet.java b/impl/src/main/java/com/sun/faces/facelets/impl/DefaultFacelet.java index 83ae43e195..824a8c42a5 100644 --- a/impl/src/main/java/com/sun/faces/facelets/impl/DefaultFacelet.java +++ b/impl/src/main/java/com/sun/faces/facelets/impl/DefaultFacelet.java @@ -230,14 +230,14 @@ public long getCreateTime() { } /** - * Delegates resolution to DefaultFaceletFactory reference. Also, caches URLs for relative paths. + * Delegates resolution to DefaultFaceletFactory reference. Also, caches URLs for absolute paths. * - * @param path a relative url path + * @param relativePath a relative url path * @return URL pointing to destination * @throws IOException if there is a problem creating the URL for the path specified */ - private URL getRelativePath(String path) throws IOException { - return factory.resolveURL(src, path); + private URL resolveURL(String relativePath) throws IOException { + return factory.resolveURL(src, relativePath); } /** @@ -268,21 +268,21 @@ private void include(DefaultFaceletContext ctx, UIComponent parent) throws IOExc /** * Used for delegation by the DefaultFaceletContext. First pulls the URL from {@link #getRelativePath(String) - * getRelativePath(String)}, then calls + * getRelativePath(String)}, then validates that the path does not represent a contracts resource, then calls * {@link #include(DefaultFaceletContext, jakarta.faces.component.UIComponent, String)}. * * @see FaceletContext#includeFacelet(UIComponent, String) * @param ctx FaceletContext to pass to the included Facelet * @param parent UIComponent to apply changes to - * @param path relative path to the desired Facelet from the FaceletContext + * @param relativePath relative path to the desired Facelet from the FaceletContext * @throws IOException * @throws FacesException * @throws FaceletException * @throws ELException */ - public void include(DefaultFaceletContext ctx, UIComponent parent, String path) throws IOException { + public void include(DefaultFaceletContext ctx, UIComponent parent, String relativePath) throws IOException { URL url; - if (path.equals(JAKARTA_FACES_ERROR_XHTML)) { + if (relativePath.equals(JAKARTA_FACES_ERROR_XHTML)) { if (isDevelopment(ctx)) { // try using this class' ClassLoader url = getErrorFacelet(DefaultFacelet.class.getClassLoader()); @@ -293,7 +293,10 @@ public void include(DefaultFaceletContext ctx, UIComponent parent, String path) return; } } else { - url = getRelativePath(path); + url = resolveURL(relativePath); + if (factory.isContractsResource(url)) { + throw new IOException("Contract resources cannot be accessed this way"); + } } this.include(ctx, parent, url); } diff --git a/impl/src/main/java/com/sun/faces/facelets/impl/DefaultFaceletFactory.java b/impl/src/main/java/com/sun/faces/facelets/impl/DefaultFaceletFactory.java index b0678ebcb4..082208d22c 100644 --- a/impl/src/main/java/com/sun/faces/facelets/impl/DefaultFaceletFactory.java +++ b/impl/src/main/java/com/sun/faces/facelets/impl/DefaultFaceletFactory.java @@ -44,6 +44,8 @@ import java.util.regex.Pattern; import com.sun.faces.RIConstants; +import com.sun.faces.application.ApplicationAssociate; +import com.sun.faces.application.resource.ResourceManager; import com.sun.faces.config.WebConfiguration; import com.sun.faces.context.FacesFileNotFoundException; import com.sun.faces.facelets.compiler.Compiler; @@ -81,7 +83,9 @@ public class DefaultFaceletFactory { // provides a custom one. The DefaultResourceResolver simply uses // the ResourceHandler to do its work. private DefaultResourceResolver resolver; + private ResourceManager manager; private URL baseUrl; + private String baseUrlAsString; private long refreshPeriod; private FaceletCache cache; private ConcurrentMap> cachePerContract; @@ -104,7 +108,9 @@ public final void init(FacesContext facesContext, Compiler compiler, DefaultReso this.compiler = compiler; cachePerContract = new ConcurrentHashMap<>(); this.resolver = resolver; + this.manager = ApplicationAssociate.getInstance(externalContext).getResourceManager(); baseUrl = resolver.resolveUrl("/"); + baseUrlAsString = baseUrl.toExternalForm(); this.idMappers = config.isOptionEnabled(UseFaceletsID) ? null : new Cache<>(new IdMapperFactory()); refreshPeriod = refreshPeriod >= 0 ? refreshPeriod * 1000 : -1; this.refreshPeriod = refreshPeriod; @@ -167,6 +173,22 @@ public URL resolveURL(URL source, String path) throws IOException { return new URL(source, path); } + /** + * Returns true if given url is a contracts resource. + * @param url source url + * @return true if given url is a contracts resource. + */ + public boolean isContractsResource(URL url) { + String urlAsString = url.toExternalForm(); + + if (!urlAsString.startsWith(baseUrlAsString)) { + return false; + } + + String path = urlAsString.substring(baseUrlAsString.length()); + return manager.isContractsResource(path); + } + /** * Create a Facelet from the passed URL. This method checks if the cached Facelet needs to be refreshed before * returning. If so, uses the passed URL to build a new instance; diff --git a/impl/src/main/java/com/sun/faces/facelets/tag/composite/InsertChildrenHandler.java b/impl/src/main/java/com/sun/faces/facelets/tag/composite/InsertChildrenHandler.java index 9386ffb60f..1c66d2b92e 100644 --- a/impl/src/main/java/com/sun/faces/facelets/tag/composite/InsertChildrenHandler.java +++ b/impl/src/main/java/com/sun/faces/facelets/tag/composite/InsertChildrenHandler.java @@ -89,7 +89,7 @@ private class RelocateChildrenListener extends RelocateListener { this.ctx = ctx; this.component = component; if (!component.getAttributes().containsKey(INDEX_ATTRIBUTE)) { - component.getAttributes().put(INDEX_ATTRIBUTE, idx); // NOTE: this is also used by AjaxBehaviorRenderer in order to detect if f:ajax was handled by cc:insertChildren. See also #5032 + component.getAttributes().put(INDEX_ATTRIBUTE, idx); } this.idx = idx; this.location = location; diff --git a/impl/src/main/java/com/sun/faces/facelets/tag/composite/RetargetedAjaxBehavior.java b/impl/src/main/java/com/sun/faces/facelets/tag/composite/RetargetedAjaxBehavior.java new file mode 100644 index 0000000000..fd5a4b53db --- /dev/null +++ b/impl/src/main/java/com/sun/faces/facelets/tag/composite/RetargetedAjaxBehavior.java @@ -0,0 +1,234 @@ +/* + * Copyright (c) 1997, 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-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: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package com.sun.faces.facelets.tag.composite; + +import java.util.Collection; +import java.util.Set; + +import jakarta.el.ValueExpression; +import jakarta.faces.component.UIComponent; +import jakarta.faces.component.behavior.AjaxBehavior; +import jakarta.faces.component.behavior.ClientBehaviorContext; +import jakarta.faces.component.behavior.ClientBehaviorHint; +import jakarta.faces.context.FacesContext; +import jakarta.faces.event.AbortProcessingException; +import jakarta.faces.event.AjaxBehaviorListener; +import jakarta.faces.event.BehaviorEvent; +import jakarta.faces.render.ClientBehaviorRenderer; + +/** + * Basically represents {@code } which is retargeted by {@code } in {@code AjaxHandler} and checked in {@code AjaxBehaviorRenderer}. + * + * We should probably introduce {@code AjaxBehaviorWrapper} in Faces.next to reduce boilerplate like this. + * + * - https://github.com/jakartaee/faces/issues/1567 + * - https://github.com/eclipse-ee4j/mojarra/issues/5032 + */ +public class RetargetedAjaxBehavior extends AjaxBehavior { + + private AjaxBehavior retargeted; + + public RetargetedAjaxBehavior(AjaxBehavior retargeted) { + this.retargeted = retargeted; + } + + @Override + public String getScript(ClientBehaviorContext behaviorContext) { + ClientBehaviorRenderer renderer = getRenderer(behaviorContext.getFacesContext()); + return renderer != null ? renderer.getScript(behaviorContext, this) : null; + } + + @Override + public void broadcast(BehaviorEvent event) throws AbortProcessingException { + retargeted.broadcast(event); + } + + @Override + public int hashCode() { + return retargeted.hashCode(); + } + + @Override + public String getRendererType() { + return retargeted.getRendererType(); + } + + @Override + public void decode(FacesContext context, UIComponent component) { + retargeted.decode(context, component); + } + + @Override + public boolean isTransient() { + return retargeted.isTransient(); + } + + @Override + public Set getHints() { + return retargeted.getHints(); + } + + @Override + public void setTransient(boolean transientFlag) { + retargeted.setTransient(transientFlag); + } + + @Override + public String getOnerror() { + return retargeted.getOnerror(); + } + + @Override + public void setOnerror(String onerror) { + retargeted.setOnerror(onerror); + } + + @Override + public boolean equals(Object obj) { + return retargeted.equals(obj); + } + + @Override + public String getOnevent() { + return retargeted.getOnevent(); + } + + @Override + public void setOnevent(String onevent) { + retargeted.setOnevent(onevent); + } + + @Override + public void markInitialState() { + retargeted.markInitialState(); + } + + @Override + public boolean initialStateMarked() { + return retargeted.initialStateMarked(); + } + + @Override + public Collection getExecute() { + return retargeted.getExecute(); + } + + @Override + public void clearInitialState() { + retargeted.clearInitialState(); + } + + @Override + public void setExecute(Collection execute) { + retargeted.setExecute(execute); + } + + @Override + public String getDelay() { + return retargeted.getDelay(); + } + + @Override + public void setDelay(String delay) { + retargeted.setDelay(delay); + } + + @Override + public Collection getRender() { + return retargeted.getRender(); + } + + @Override + public void setRender(Collection render) { + retargeted.setRender(render); + } + + @Override + public boolean isResetValues() { + return retargeted.isResetValues(); + } + + @Override + public void setResetValues(boolean resetValues) { + retargeted.setResetValues(resetValues); + } + + @Override + public boolean isDisabled() { + return retargeted.isDisabled(); + } + + @Override + public void setDisabled(boolean disabled) { + retargeted.setDisabled(disabled); + } + + @Override + public boolean isImmediate() { + return retargeted.isImmediate(); + } + + @Override + public void setImmediate(boolean immediate) { + retargeted.setImmediate(immediate); + } + + @Override + public boolean isImmediateSet() { + return retargeted.isImmediateSet(); + } + + @Override + public String toString() { + return retargeted.toString(); + } + + @Override + public boolean isResetValuesSet() { + return retargeted.isResetValuesSet(); + } + + @Override + public ValueExpression getValueExpression(String name) { + return retargeted.getValueExpression(name); + } + + @Override + public void setValueExpression(String name, ValueExpression binding) { + retargeted.setValueExpression(name, binding); + } + + @Override + public void addAjaxBehaviorListener(AjaxBehaviorListener listener) { + retargeted.addAjaxBehaviorListener(listener); + } + + @Override + public void removeAjaxBehaviorListener(AjaxBehaviorListener listener) { + retargeted.removeAjaxBehaviorListener(listener); + } + + @Override + public Object saveState(FacesContext context) { + return retargeted.saveState(context); + } + + @Override + public void restoreState(FacesContext context, Object state) { + retargeted.restoreState(context, state); + } +} diff --git a/impl/src/main/java/com/sun/faces/facelets/tag/faces/ComponentTagHandlerDelegateImpl.java b/impl/src/main/java/com/sun/faces/facelets/tag/faces/ComponentTagHandlerDelegateImpl.java index 4a9b9c1637..3a58883cce 100644 --- a/impl/src/main/java/com/sun/faces/facelets/tag/faces/ComponentTagHandlerDelegateImpl.java +++ b/impl/src/main/java/com/sun/faces/facelets/tag/faces/ComponentTagHandlerDelegateImpl.java @@ -462,10 +462,7 @@ protected UIComponent findChild(FaceletContext ctx, UIComponent parent, String t protected UIComponent findReparentedComponent(FaceletContext ctx, UIComponent parent, String tagId) { UIComponent facet = parent.getFacets().get(UIComponent.COMPOSITE_FACET_NAME); if (facet != null) { - UIComponent newParent = facet.findComponent((String) parent.getAttributes().get(tagId)); - if (newParent != null) { - return ComponentSupport.findChildByTagId(ctx.getFacesContext(), newParent, tagId); - } + return findChild(ctx, facet, tagId); } return null; } diff --git a/impl/src/main/java/com/sun/faces/facelets/tag/faces/core/AjaxHandler.java b/impl/src/main/java/com/sun/faces/facelets/tag/faces/core/AjaxHandler.java index eef385f574..dda42ad98d 100644 --- a/impl/src/main/java/com/sun/faces/facelets/tag/faces/core/AjaxHandler.java +++ b/impl/src/main/java/com/sun/faces/facelets/tag/faces/core/AjaxHandler.java @@ -32,6 +32,7 @@ import com.sun.faces.component.behavior.AjaxBehaviors; import com.sun.faces.facelets.tag.TagHandlerImpl; import com.sun.faces.facelets.tag.composite.BehaviorHolderWrapper; +import com.sun.faces.facelets.tag.composite.RetargetedAjaxBehavior; import com.sun.faces.facelets.tag.faces.CompositeComponentTagHandler; import com.sun.faces.renderkit.RenderKitUtils; @@ -313,6 +314,7 @@ private AjaxBehavior createAjaxBehavior(FaceletContext ctx, UIComponent parent, executeClientIds.remove("@this"); stream(targetClientIds.trim().split(" +")).map(id -> "@this" + separatorChar + id).forEach(executeClientIds::add); behavior.setExecute(executeClientIds); + behavior = new RetargetedAjaxBehavior(behavior); } } } diff --git a/impl/src/main/java/com/sun/faces/facelets/tag/ui/CompositionHandler.java b/impl/src/main/java/com/sun/faces/facelets/tag/ui/CompositionHandler.java index 075c1d3ae4..cf004f77a4 100644 --- a/impl/src/main/java/com/sun/faces/facelets/tag/ui/CompositionHandler.java +++ b/impl/src/main/java/com/sun/faces/facelets/tag/ui/CompositionHandler.java @@ -25,7 +25,6 @@ import java.util.logging.Level; import java.util.logging.Logger; -import com.sun.faces.config.WebConfiguration; import com.sun.faces.facelets.FaceletContextImplBase; import com.sun.faces.facelets.TemplateClient; import com.sun.faces.facelets.el.VariableMapperWrapper; @@ -127,16 +126,12 @@ public void apply(FaceletContext ctxObj, UIComponent parent) throws IOException if (path.trim().length() == 0) { throw new TagAttributeException(tag, template, "Invalid path : " + path); } - WebConfiguration webConfig = WebConfiguration.getInstance(); - if (path.startsWith(webConfig.getOptionValue(WebConfiguration.WebContextInitParameter.WebAppContractsDirectory))) { - throw new TagAttributeException(tag, template, "Invalid path, contract resources cannot be accessed this way : " + path); - } ctx.includeFacelet(parent, path); } catch (IOException e) { if (log.isLoggable(Level.FINE)) { log.log(Level.FINE, e.toString(), e); } - throw new TagAttributeException(tag, template, "Invalid path : " + path); + throw new TagAttributeException(tag, template, "Invalid path : " + path, e); } finally { ctx.popClient(this); ctx.setVariableMapper(orig); diff --git a/impl/src/main/java/com/sun/faces/facelets/tag/ui/DecorateHandler.java b/impl/src/main/java/com/sun/faces/facelets/tag/ui/DecorateHandler.java index 0d500e5dd6..7af0fbf184 100644 --- a/impl/src/main/java/com/sun/faces/facelets/tag/ui/DecorateHandler.java +++ b/impl/src/main/java/com/sun/faces/facelets/tag/ui/DecorateHandler.java @@ -25,7 +25,6 @@ import java.util.logging.Level; import java.util.logging.Logger; -import com.sun.faces.config.WebConfiguration; import com.sun.faces.facelets.FaceletContextImplBase; import com.sun.faces.facelets.TemplateClient; import com.sun.faces.facelets.el.VariableMapperWrapper; @@ -108,16 +107,12 @@ public void apply(FaceletContext ctxObj, UIComponent parent) throws IOException if (path.trim().length() == 0) { throw new TagAttributeException(tag, template, "Invalid path : " + path); } - WebConfiguration webConfig = WebConfiguration.getInstance(); - if (path.startsWith(webConfig.getOptionValue(WebConfiguration.WebContextInitParameter.WebAppContractsDirectory))) { - throw new TagAttributeException(tag, template, "Invalid path, contract resources cannot be accessed this way : " + path); - } ctx.includeFacelet(parent, path); } catch (IOException e) { if (log.isLoggable(Level.FINE)) { log.log(Level.FINE, e.toString(), e); } - throw new TagAttributeException(tag, template, "Invalid path : " + path); + throw new TagAttributeException(tag, template, "Invalid path : " + path, e); } finally { ctx.setVariableMapper(orig); ctx.popClient(this); diff --git a/impl/src/main/java/com/sun/faces/facelets/tag/ui/IncludeHandler.java b/impl/src/main/java/com/sun/faces/facelets/tag/ui/IncludeHandler.java index f78c6dacae..0704901d5d 100644 --- a/impl/src/main/java/com/sun/faces/facelets/tag/ui/IncludeHandler.java +++ b/impl/src/main/java/com/sun/faces/facelets/tag/ui/IncludeHandler.java @@ -20,7 +20,6 @@ import java.util.logging.Level; import java.util.logging.Logger; -import com.sun.faces.config.WebConfiguration; import com.sun.faces.facelets.el.VariableMapperWrapper; import com.sun.faces.facelets.tag.TagHandlerImpl; import com.sun.faces.util.FacesLogger; @@ -76,16 +75,12 @@ public void apply(FaceletContext ctx, UIComponent parent) throws IOException { ctx.setVariableMapper(new VariableMapperWrapper(orig)); try { nextHandler.apply(ctx, null); - WebConfiguration webConfig = WebConfiguration.getInstance(); - if (path.startsWith(webConfig.getOptionValue(WebConfiguration.WebContextInitParameter.WebAppContractsDirectory))) { - throw new TagAttributeException(tag, src, "Invalid src, contract resources cannot be accessed this way : " + path); - } ctx.includeFacelet(parent, path); } catch (IOException e) { if (log.isLoggable(Level.FINE)) { log.log(Level.FINE, e.toString(), e); } - throw new TagAttributeException(tag, src, "Invalid path : " + path); + throw new TagAttributeException(tag, src, "Invalid path : " + path, e); } finally { ctx.setVariableMapper(orig); } diff --git a/impl/src/main/java/com/sun/faces/facelets/util/Classpath.java b/impl/src/main/java/com/sun/faces/facelets/util/Classpath.java index 7359e3ef50..ed8bec58bb 100644 --- a/impl/src/main/java/com/sun/faces/facelets/util/Classpath.java +++ b/impl/src/main/java/com/sun/faces/facelets/util/Classpath.java @@ -16,6 +16,8 @@ package com.sun.faces.facelets.util; +import static java.nio.charset.StandardCharsets.UTF_8; + import java.io.File; import java.io.IOException; import java.io.InputStream; @@ -23,7 +25,6 @@ import java.net.URL; import java.net.URLConnection; import java.net.URLDecoder; -import java.nio.charset.StandardCharsets; import java.util.Enumeration; import java.util.LinkedHashSet; import java.util.Set; @@ -46,13 +47,6 @@ public final class Classpath { private static final String[] PREFIXES_TO_EXCLUDE = { "rar:", "sar:" }; private static final String[] EXTENSIONS_TO_EXCLUDE = { ".rar", ".sar" }; - /** - * - */ - public Classpath() { - super(); - } - public enum SearchAdvice { FirstMatchOnly, AllMatches } @@ -94,7 +88,7 @@ public static URL[] search(ClassLoader cl, String prefix, String suffix, SearchA if (jarFile != null) { searchJar(cl, all, jarFile, prefix, suffix, advice); } else { - boolean searchDone = searchDir(all, new File(URLDecoder.decode(url.getFile(), StandardCharsets.UTF_8)), suffix); + boolean searchDone = searchDir(all, new File(URLDecoder.decode(url.getFile(), UTF_8)), suffix); if (!searchDone) { searchFromURL(all, prefix, suffix, url); } @@ -245,7 +239,7 @@ static JarFile getAlternativeJarFile(String urlFile) throws IOException { // And trim off any "file:" prefix. if (jarFileUrl.startsWith("file:")) { jarFileUrl = jarFileUrl.substring("file:".length()); - jarFileUrl = URLDecoder.decode(jarFileUrl, StandardCharsets.UTF_8); + jarFileUrl = URLDecoder.decode(jarFileUrl, UTF_8); } boolean foundExclusion = false; for (int i = 0; i < PREFIXES_TO_EXCLUDE.length; i++) { diff --git a/impl/src/main/java/com/sun/faces/lifecycle/ClientWindowImpl.java b/impl/src/main/java/com/sun/faces/lifecycle/ClientWindowImpl.java index 5f1cabd651..b0d3cc3017 100644 --- a/impl/src/main/java/com/sun/faces/lifecycle/ClientWindowImpl.java +++ b/impl/src/main/java/com/sun/faces/lifecycle/ClientWindowImpl.java @@ -16,6 +16,7 @@ package com.sun.faces.lifecycle; +import static com.sun.faces.context.SessionMap.getMutex; import static com.sun.faces.renderkit.RenderKitUtils.PredefinedPostbackParameter.CLIENT_WINDOW_PARAM; import java.util.Map; @@ -55,7 +56,7 @@ public void decode(FacesContext context) { } private String calculateClientWindow(FacesContext context) { - synchronized (context.getExternalContext().getSession(true)) { + synchronized (getMutex(context.getExternalContext().getSession(true))) { final String clientWindowCounterKey = "com.sun.faces.lifecycle.ClientWindowCounterKey"; ExternalContext extContext = context.getExternalContext(); Map sessionAttrs = extContext.getSessionMap(); diff --git a/impl/src/main/java/com/sun/faces/lifecycle/LifecycleImpl.java b/impl/src/main/java/com/sun/faces/lifecycle/LifecycleImpl.java index 42a6b9577a..9811f3620a 100644 --- a/impl/src/main/java/com/sun/faces/lifecycle/LifecycleImpl.java +++ b/impl/src/main/java/com/sun/faces/lifecycle/LifecycleImpl.java @@ -66,46 +66,18 @@ public class LifecycleImpl extends Lifecycle { // List for registered PhaseListeners private List listeners = new CopyOnWriteArrayList<>(); - private boolean isClientWindowEnabled = false; - private WebConfiguration config; public LifecycleImpl() { - } public LifecycleImpl(FacesContext context) { - ExternalContext extContext = context.getExternalContext(); - config = WebConfiguration.getInstance(extContext); - context.getApplication().subscribeToEvent(PostConstructApplicationEvent.class, Application.class, new PostConstructApplicationListener()); - - } - - private class PostConstructApplicationListener implements SystemEventListener { - - @Override - public boolean isListenerForSource(Object source) { - return source instanceof Application; - } - - @Override - public void processEvent(SystemEvent event) throws AbortProcessingException { - postConstructApplicationInitialization(); - } - } - private void postConstructApplicationInitialization() { - String optionValue = config.getOptionValue(WebConfiguration.WebContextInitParameter.ClientWindowMode); - isClientWindowEnabled = null != optionValue && !optionValue.equals(WebConfiguration.WebContextInitParameter.ClientWindowMode.getDefaultValue()); - } // ------------------------------------------------------- Lifecycle Methods @Override public void attachWindow(FacesContext context) { - if (!isClientWindowEnabled) { - return; - } if (context == null) { throw new NullPointerException(MessageUtils.getExceptionMessageString(MessageUtils.NULL_PARAMETERS_ERROR_MESSAGE_ID, "context")); } diff --git a/impl/src/main/java/com/sun/faces/lifecycle/RestoreViewPhase.java b/impl/src/main/java/com/sun/faces/lifecycle/RestoreViewPhase.java index a869c08382..74af50b913 100644 --- a/impl/src/main/java/com/sun/faces/lifecycle/RestoreViewPhase.java +++ b/impl/src/main/java/com/sun/faces/lifecycle/RestoreViewPhase.java @@ -25,13 +25,14 @@ import static com.sun.faces.util.MessageUtils.getExceptionMessageString; import static com.sun.faces.util.Util.getViewHandler; import static com.sun.faces.util.Util.isOneOf; +import static jakarta.faces.component.visit.VisitHint.SKIP_ITERATION; import static jakarta.faces.event.PhaseId.RESTORE_VIEW; import static jakarta.faces.render.ResponseStateManager.NON_POSTBACK_VIEW_TOKEN_PARAM; import static jakarta.faces.view.ViewMetadata.hasMetadata; +import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.logging.Level.FINE; import static java.util.logging.Level.SEVERE; -import java.io.UnsupportedEncodingException; import java.net.URI; import java.net.URISyntaxException; import java.net.URLEncoder; @@ -80,7 +81,7 @@ public class RestoreViewPhase extends Phase { private static final String WEBAPP_ERROR_PAGE_MARKER = "jakarta.servlet.error.message"; private static final Logger LOGGER = FacesLogger.LIFECYCLE.getLogger(); - private static String SKIP_ITERATION_HINT = "jakarta.faces.visit.SKIP_ITERATION"; + private static final Set SKIP_ITERATION_HINT = EnumSet.of(SKIP_ITERATION); // ---------------------------------------------------------- Public Methods @@ -101,7 +102,7 @@ public void doPhase(FacesContext context, Lifecycle lifecycle, ListIterator - * + * * POSTCONDITION: The facesContext has been initialized with a tree. */ @@ -248,16 +249,7 @@ private void maybeTakeProtectedViewAction(FacesContext context, ViewHandler view String incomingSecretKeyValue = extContext.getRequestParameterMap().get(NON_POSTBACK_VIEW_TOKEN_PARAM); if (incomingSecretKeyValue != null) { - try { - incomingSecretKeyValue = URLEncoder.encode(incomingSecretKeyValue, "UTF-8"); - } catch (UnsupportedEncodingException e) { - if (LOGGER.isLoggable(SEVERE)) { - LOGGER.log(SEVERE, - "Unable to re-encode value of request parameter " + NON_POSTBACK_VIEW_TOKEN_PARAM + ":" - + incomingSecretKeyValue, e); - } - incomingSecretKeyValue = null; - } + incomingSecretKeyValue = URLEncoder.encode(incomingSecretKeyValue, UTF_8); } String correctSecretKeyValue = rsm.getCryptographicallyStrongTokenFromSession(context); @@ -382,14 +374,8 @@ private void deliverPostRestoreStateEvent(FacesContext facesContext) throws Face UIViewRoot root = facesContext.getViewRoot(); PostRestoreStateEvent postRestoreStateEvent = new PostRestoreStateEvent(root); try { - // PENDING: This is included for those component frameworks that don't utilize the - // new VisitHint(s) yet - but still wish to know that they should be non-iterating - // during state saving. It should be removed at some point. - facesContext.getAttributes().put(SKIP_ITERATION_HINT, true); facesContext.getApplication().publishEvent(facesContext, PostRestoreStateEvent.class, root); - - Set hints = EnumSet.of(VisitHint.SKIP_ITERATION); - VisitContext visitContext = VisitContext.createVisitContext(facesContext, null, hints); + VisitContext visitContext = VisitContext.createVisitContext(facesContext, null, SKIP_ITERATION_HINT); root.visitTree(visitContext, (context, target) -> { postRestoreStateEvent.setComponent(target); target.processEvent(postRestoreStateEvent); @@ -401,11 +387,6 @@ private void deliverPostRestoreStateEvent(FacesContext facesContext) throws Face .publishEvent( facesContext, ExceptionQueuedEvent.class, new ExceptionQueuedEventContext(facesContext, e, null, PhaseId.RESTORE_VIEW)); - } finally { - // PENDING: This is included for those component frameworks that don't utilize the - // new VisitHint(s) yet - but still wish to know that they should be non-iterating - // during state saving. It should be removed at some point. - facesContext.getAttributes().remove(SKIP_ITERATION_HINT); } } diff --git a/impl/src/main/java/com/sun/faces/renderkit/RenderKitUtils.java b/impl/src/main/java/com/sun/faces/renderkit/RenderKitUtils.java index 36b57a3b43..a1bbd394cd 100644 --- a/impl/src/main/java/com/sun/faces/renderkit/RenderKitUtils.java +++ b/impl/src/main/java/com/sun/faces/renderkit/RenderKitUtils.java @@ -37,6 +37,7 @@ import java.util.logging.Logger; import com.sun.faces.RIConstants; +import com.sun.faces.application.ApplicationAssociate; import com.sun.faces.config.WebConfiguration; import com.sun.faces.el.ELUtils; import com.sun.faces.facelets.util.DevTools; @@ -1219,9 +1220,8 @@ public static String getImageSource(FacesContext context, UIComponent component, ResourceHandler handler = context.getApplication().getResourceHandler(); if (resName != null) { String libName = (String) component.getAttributes().get("library"); - WebConfiguration webConfig = WebConfiguration.getInstance(); - - if (libName == null && resName.startsWith(webConfig.getOptionValue(WebConfiguration.WebContextInitParameter.WebAppContractsDirectory))) { + + if (libName == null && ApplicationAssociate.getInstance(context).getResourceManager().isContractsResource(resName)) { if (context.isProjectStage(ProjectStage.Development)) { String msg = "Illegal path, direct contract references are not allowed: " + resName; context.addMessage(component.getClientId(context), new FacesMessage(FacesMessage.SEVERITY_ERROR, msg, msg)); diff --git a/impl/src/main/java/com/sun/faces/renderkit/ServerSideStateHelper.java b/impl/src/main/java/com/sun/faces/renderkit/ServerSideStateHelper.java index 5e0e271293..5c96f298aa 100644 --- a/impl/src/main/java/com/sun/faces/renderkit/ServerSideStateHelper.java +++ b/impl/src/main/java/com/sun/faces/renderkit/ServerSideStateHelper.java @@ -22,6 +22,7 @@ import static com.sun.faces.config.WebConfiguration.BooleanWebContextInitParameter.SerializeServerState; import static com.sun.faces.config.WebConfiguration.WebContextInitParameter.NumberOfLogicalViews; import static com.sun.faces.config.WebConfiguration.WebContextInitParameter.NumberOfViews; +import static com.sun.faces.context.SessionMap.getMutex; import static com.sun.faces.renderkit.RenderKitUtils.PredefinedPostbackParameter.VIEW_STATE_PARAM; import static com.sun.faces.util.Util.notNull; import static java.util.logging.Level.FINE; @@ -149,7 +150,7 @@ public void writeState(FacesContext ctx, Object state, StringBuilder stateCaptur Object sessionObj = externalContext.getSession(true); Map sessionMap = externalContext.getSessionMap(); - synchronized (sessionObj) { + synchronized (getMutex(sessionObj)) { Map logicalMap = TypedCollections.dynamicallyCastMap((Map) sessionMap.get(LOGICAL_VIEW_MAP), String.class, Map.class); if (logicalMap == null) { logicalMap = Collections.synchronizedMap(new LRUMap(numberOfLogicalViews)); @@ -262,7 +263,7 @@ public Object getState(FacesContext ctx, String viewId) { } // noinspection SynchronizationOnLocalVariableOrMethodParameter - synchronized (sessionObj) { + synchronized (getMutex(sessionObj)) { Map logicalMap = (Map) externalCtx.getSessionMap().get(LOGICAL_VIEW_MAP); if (logicalMap != null) { Map actualMap = (Map) logicalMap.get(idInLogicalMap); @@ -328,7 +329,7 @@ protected Object handleSaveState(Object state) { if (!webConfig.isOptionEnabled(SerializeServerState)) { return state; } - + ByteArrayOutputStream baos = new ByteArrayOutputStream(1024); ObjectOutputStream oas = null; try { @@ -346,7 +347,7 @@ protected Object handleSaveState(Object state) { } } } - + return baos.toByteArray(); } @@ -359,7 +360,7 @@ protected Object handleRestoreState(Object state) { if (!webConfig.isOptionEnabled(SerializeServerState)) { return state; } - + try (ByteArrayInputStream bais = new ByteArrayInputStream((byte[]) state); ObjectInputStream ois = serialProvider.createObjectInputStream(compressViewState ? new GZIPInputStream(bais, 1024) : bais);) { return ois.readObject(); @@ -402,7 +403,7 @@ public boolean isStateless(FacesContext facesContext, String viewId) throws Ille if (!facesContext.isPostback()) { throw new IllegalStateException("Cannot determine whether or not the request is stateless"); } - + String compoundId = getStateParamValue(facesContext); return compoundId != null && "stateless".equals(compoundId); } diff --git a/impl/src/main/java/com/sun/faces/renderkit/StateHelper.java b/impl/src/main/java/com/sun/faces/renderkit/StateHelper.java index fb53004d6f..b865c9d165 100644 --- a/impl/src/main/java/com/sun/faces/renderkit/StateHelper.java +++ b/impl/src/main/java/com/sun/faces/renderkit/StateHelper.java @@ -21,10 +21,10 @@ import static com.sun.faces.renderkit.RenderKitUtils.PredefinedPostbackParameter.CLIENT_WINDOW_PARAM; import static com.sun.faces.renderkit.RenderKitUtils.PredefinedPostbackParameter.RENDER_KIT_ID_PARAM; import static com.sun.faces.renderkit.RenderKitUtils.PredefinedPostbackParameter.VIEW_STATE_PARAM; +import static java.nio.charset.StandardCharsets.UTF_8; import java.io.IOException; import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; import java.util.logging.Logger; import com.sun.faces.RIConstants; @@ -112,7 +112,7 @@ public static void createAndStoreCryptographicallyStrongTokenInSession(HttpSessi ByteArrayGuardAESCTR guard = new ByteArrayGuardAESCTR(); String clearText = String.valueOf(System.currentTimeMillis()); String result = guard.encrypt(clearText); - result = URLEncoder.encode(result, StandardCharsets.UTF_8); + result = URLEncoder.encode(result, UTF_8); session.setAttribute(TOKEN_NAME, result); } @@ -167,7 +167,7 @@ protected static String getStateParamValue(FacesContext context) { if (pValue != null && pValue.length() == 0) { pValue = null; } - + return pValue; } diff --git a/impl/src/main/java/com/sun/faces/renderkit/html_basic/AjaxBehaviorRenderer.java b/impl/src/main/java/com/sun/faces/renderkit/html_basic/AjaxBehaviorRenderer.java index 7fea1c875a..31842eff93 100644 --- a/impl/src/main/java/com/sun/faces/renderkit/html_basic/AjaxBehaviorRenderer.java +++ b/impl/src/main/java/com/sun/faces/renderkit/html_basic/AjaxBehaviorRenderer.java @@ -26,7 +26,7 @@ import java.util.logging.Level; import java.util.logging.Logger; -import com.sun.faces.facelets.tag.composite.InsertChildrenHandler; +import com.sun.faces.facelets.tag.composite.RetargetedAjaxBehavior; import com.sun.faces.renderkit.RenderKitUtils; import com.sun.faces.util.FacesLogger; @@ -221,9 +221,9 @@ private static String buildAjaxCommand(ClientBehaviorContext behaviorContext, Aj ajaxCommand.append(eventName); ajaxCommand.append("',"); - appendIds(behaviorContext.getFacesContext(), component, ajaxCommand, execute); + appendIds(behaviorContext.getFacesContext(), component, ajaxBehavior, ajaxCommand, execute); ajaxCommand.append(","); - appendIds(behaviorContext.getFacesContext(), component, ajaxCommand, render); + appendIds(behaviorContext.getFacesContext(), component, ajaxBehavior, ajaxCommand, render); if (onevent != null || onerror != null || delay != null || resetValues != null || !params.isEmpty()) { @@ -280,7 +280,7 @@ private static String buildAjaxCommand(ClientBehaviorContext behaviorContext, Aj SearchExpressionHint.RESOLVE_SINGLE_COMPONENT); // Appends an ids argument to the ajax command - private static void appendIds(FacesContext facesContext, UIComponent component, StringBuilder builder, Collection ids) { + private static void appendIds(FacesContext facesContext, UIComponent component, AjaxBehavior ajaxBehavior, StringBuilder builder, Collection ids) { if (null == ids || ids.isEmpty()) { builder.append('0'); @@ -311,7 +311,7 @@ private static void appendIds(FacesContext facesContext, UIComponent component, boolean clientResolveableExpression = expression.equals("@all") || expression.equals("@none") || expression.equals("@form") || expression.equals("@this"); - if (composite != null && !isHandledByInsertChildren(component, composite) && (expression.equals("@this") || expression.startsWith("@this" + separatorChar))) { + if (composite != null && (ajaxBehavior instanceof RetargetedAjaxBehavior) && (expression.equals("@this") || expression.startsWith("@this" + separatorChar))) { expression = expression.replaceFirst("@this", separatorChar + composite.getClientId(facesContext)); clientResolveableExpression = false; } @@ -347,16 +347,6 @@ private static void appendIds(FacesContext facesContext, UIComponent component, builder.append("'"); } - private static boolean isHandledByInsertChildren(UIComponent component, UIComponent composite) { - for (UIComponent parent = component.getParent(); parent != null && !parent.equals(composite); parent = parent.getParent()) { - if (parent.getAttributes().containsKey(InsertChildrenHandler.INDEX_ATTRIBUTE)) { - return true; - } - } - - return false; - } - // Returns the resolved (client id) for a particular id. private static String getResolvedId(UIComponent component, String id) { diff --git a/impl/src/main/java/com/sun/faces/renderkit/html_basic/OutputLinkRenderer.java b/impl/src/main/java/com/sun/faces/renderkit/html_basic/OutputLinkRenderer.java index 2232224579..20d155f4d7 100644 --- a/impl/src/main/java/com/sun/faces/renderkit/html_basic/OutputLinkRenderer.java +++ b/impl/src/main/java/com/sun/faces/renderkit/html_basic/OutputLinkRenderer.java @@ -19,11 +19,11 @@ package com.sun.faces.renderkit.html_basic; import static com.sun.faces.util.Util.componentIsDisabled; +import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.logging.Level.FINE; import java.io.IOException; import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; import com.sun.faces.renderkit.Attribute; import com.sun.faces.renderkit.AttributeManager; @@ -36,7 +36,7 @@ /** * OutputLinkRenderer is a class ... - * + * * Lifetime And Scope * */ @@ -134,7 +134,7 @@ protected Object getValue(UIComponent component) { if (componentIsDisabled(component)) { return null; } - + return ((UIOutput) component).getValue(); } @@ -177,10 +177,10 @@ protected void renderAsActive(FacesContext context, UIComponent component) throw if (pn != null && pn.length() != 0) { String pv = param.value; sb.append(paramWritten ? '&' : '?'); - sb.append(URLEncoder.encode(pn, StandardCharsets.UTF_8)); + sb.append(URLEncoder.encode(pn, UTF_8)); sb.append('='); if (pv != null && pv.length() != 0) { - sb.append(URLEncoder.encode(pv, StandardCharsets.UTF_8)); + sb.append(URLEncoder.encode(pv, UTF_8)); } paramWritten = true; } diff --git a/impl/src/main/java/com/sun/faces/renderkit/html_basic/ScriptStyleBaseRenderer.java b/impl/src/main/java/com/sun/faces/renderkit/html_basic/ScriptStyleBaseRenderer.java index 39dc2b9fd9..838c722ae8 100644 --- a/impl/src/main/java/com/sun/faces/renderkit/html_basic/ScriptStyleBaseRenderer.java +++ b/impl/src/main/java/com/sun/faces/renderkit/html_basic/ScriptStyleBaseRenderer.java @@ -21,7 +21,7 @@ import java.util.Map; import java.util.logging.Logger; -import com.sun.faces.config.WebConfiguration; +import com.sun.faces.application.ApplicationAssociate; import com.sun.faces.util.FacesLogger; import jakarta.faces.application.FacesMessage; @@ -194,9 +194,7 @@ public void encodeEnd(FacesContext context, UIComponent component) throws IOExce ResponseWriter writer = context.getResponseWriter(); startExternalElement(context, writer, component); - WebConfiguration webConfig = WebConfiguration.getInstance(); - - if (library == null && name != null && name.startsWith(webConfig.getOptionValue(WebConfiguration.WebContextInitParameter.WebAppContractsDirectory))) { + if (library == null && name != null && ApplicationAssociate.getInstance(context).getResourceManager().isContractsResource(name)) { if (context.isProjectStage(ProjectStage.Development)) { String msg = "Illegal path, direct contract references are not allowed: " + name; context.addMessage(component.getClientId(context), new FacesMessage(FacesMessage.SEVERITY_ERROR, msg, msg)); 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 64bc853a06..d445f08854 100644 --- a/impl/src/main/java/com/sun/faces/util/Util.java +++ b/impl/src/main/java/com/sun/faces/util/Util.java @@ -19,16 +19,20 @@ package com.sun.faces.util; +import static com.sun.faces.RIConstants.FACELETS_ENCODING_KEY; import static com.sun.faces.RIConstants.FACES_SERVLET_MAPPINGS; import static com.sun.faces.RIConstants.FACES_SERVLET_REGISTRATION; +import static com.sun.faces.RIConstants.NO_VALUE; import static com.sun.faces.util.MessageUtils.ILLEGAL_ATTEMPT_SETTING_APPLICATION_ARTIFACT_ID; import static com.sun.faces.util.MessageUtils.NAMED_OBJECT_NOT_FOUND_ERROR_MESSAGE_ID; import static com.sun.faces.util.MessageUtils.NULL_PARAMETERS_ERROR_MESSAGE_ID; import static com.sun.faces.util.MessageUtils.NULL_VIEW_ID_ERROR_MESSAGE_ID; import static com.sun.faces.util.MessageUtils.getExceptionMessageString; +import static jakarta.faces.application.ViewHandler.CHARACTER_ENCODING_KEY; import static java.lang.Character.isDigit; 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 java.beans.FeatureDescriptor; @@ -64,6 +68,7 @@ import javax.naming.InitialContext; import javax.naming.NamingException; +import javax.xml.XMLConstants; import javax.xml.namespace.NamespaceContext; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; @@ -110,7 +115,7 @@ /** * Util is a class ... - * + * * Lifetime And Scope * */ @@ -274,12 +279,35 @@ public static boolean isUnitTestModeEnabled() { return unitTestModeEnabled; } + public static interface ThrowingBiConsumer { + void accept(T t, U u) throws Exception; + } + + private static void setFeature(ThrowingBiConsumer setter, F feature, Boolean flag) { + try { + setter.accept(feature, flag); + } catch (Exception e) { + throw new IllegalArgumentException("The feature '" + feature + "' is not supported by your XML processor.", e); + } + } + + private static void setPossiblyUnsupportedFeature(ThrowingBiConsumer setter, F feature, Boolean flag) { + try { + setFeature(setter, feature, flag); + } catch (IllegalArgumentException e) { + LOGGER.log(Level.FINE, e.getMessage(), e); + } + } + public static TransformerFactory createTransformerFactory() { ClassLoader cl = Thread.currentThread().getContextClassLoader(); TransformerFactory factory; try { Thread.currentThread().setContextClassLoader(Util.class.getClassLoader()); factory = TransformerFactory.newInstance(); + factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, NO_VALUE); + factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, NO_VALUE); + setFeature(factory::setFeature, XMLConstants.FEATURE_SECURE_PROCESSING, true); } finally { Thread.currentThread().setContextClassLoader(cl); } @@ -303,13 +331,25 @@ public static DocumentBuilderFactory createDocumentBuilderFactory() { DocumentBuilderFactory factory; try { Thread.currentThread().setContextClassLoader(Util.class.getClassLoader()); - factory = DocumentBuilderFactory.newInstance(); + factory = createLocalDocumentBuilderFactory(); } finally { Thread.currentThread().setContextClassLoader(cl); } return factory; } + public static DocumentBuilderFactory createLocalDocumentBuilderFactory() { + DocumentBuilderFactory factory; + factory = DocumentBuilderFactory.newInstance(); + factory.setXIncludeAware(false); + factory.setExpandEntityReferences(false); + setFeature(factory::setFeature, XMLConstants.FEATURE_SECURE_PROCESSING, true); + setPossiblyUnsupportedFeature(factory::setFeature, "http://xml.org/sax/features/external-general-entities", false); + setPossiblyUnsupportedFeature(factory::setFeature, "http://xml.org/sax/features/external-parameter-entities", false); + setPossiblyUnsupportedFeature(factory::setFeature, "http://apache.org/xml/features/nonvalidating/load-external-dtd", false); + return factory; + } + public static SchemaFactory createSchemaFactory(String uri) { ClassLoader cl = Thread.currentThread().getContextClassLoader(); SchemaFactory factory; @@ -1650,4 +1690,80 @@ public static int extractFirstNumericSegment(String clientId, char separatorChar throw new NumberFormatException("there is no numeric segment"); } + /** + * @param context the {@link FacesContext} for the current request + * @return the encoding to be used for the response + */ + public static String getResponseEncoding(FacesContext context) { + return getResponseEncoding(context, Optional.empty()); + } + + /** + * @param context the {@link FacesContext} for the current request + * @param defaultEncoding the default encoding, if any + * @return the encoding to be used for the response + */ + public static String getResponseEncoding(FacesContext context, Optional defaultEncoding) { + + // 1. First get it from viewroot, if any. + if (context.getViewRoot() != null) { + String encoding = (String) context.getViewRoot().getAttributes().get(FACELETS_ENCODING_KEY); + + if (encoding != null) { + // If found, then immediately return it, this represents either the encoding explicitly set via or the one actually set on response. + // See also ViewHandler#apply() and FaceletViewHandlingStrategy#createResponseWriter(). + return encoding; + } + } + + // 2. If none found then get it from context (this is usually set during compile/buildtime based on request character encoding). + // See also SAXCompiler#doCompile() and EncodingHandler#apply(). + String encoding = (String) context.getAttributes().get(FACELETS_ENCODING_KEY); + + if (encoding != null && LOGGER.isLoggable(FINEST)) { + LOGGER.log(FINEST, "Using Facelet encoding {0}", encoding); + } + + if (encoding == null) { + // 3. If none found then get it from request (could happen when the view isn't built yet). + // See also ViewHandler#initView() and ViewHandler#calculateCharacterEncoding(). + encoding = context.getExternalContext().getRequestCharacterEncoding(); + + if (encoding != null && LOGGER.isLoggable(FINEST)) { + LOGGER.log(FINEST, "Using Request encoding {0}", encoding); + } + } + + 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(). + encoding = (String) context.getExternalContext().getSessionMap().get(CHARACTER_ENCODING_KEY); + + if (encoding != null && LOGGER.isLoggable(FINEST)) { + LOGGER.log(FINEST, "Using Session encoding {0}", encoding); + } + } + + if (encoding == null) { + // 5. If still none found then fall back to specified default. + encoding = defaultEncoding.get(); + + if (encoding != null && !encoding.isBlank()) { + if (LOGGER.isLoggable(FINEST)) { + LOGGER.log(FINEST, "Using specified default encoding {0}", encoding); + } + } + else { + // 6. If specified default is null or blank then finally fall back to hardcoded default. + encoding = RIConstants.CHAR_ENCODING; + + if (LOGGER.isLoggable(FINEST)) { + LOGGER.log(FINEST, "No encoding found, defaulting to {0}", encoding); + } + } + } + + return encoding; + } + } diff --git a/impl/src/main/java/jakarta/faces/application/ViewHandler.java b/impl/src/main/java/jakarta/faces/application/ViewHandler.java index d4c868349a..9baecf1532 100644 --- a/impl/src/main/java/jakarta/faces/application/ViewHandler.java +++ b/impl/src/main/java/jakarta/faces/application/ViewHandler.java @@ -203,12 +203,17 @@ public abstract class ViewHandler { * */ public void initView(FacesContext context) throws FacesException { - String encoding = context.getExternalContext().getRequestCharacterEncoding(); - if (encoding != null) { + String originalEncoding = context.getExternalContext().getRequestCharacterEncoding(); + String encoding = (originalEncoding != null) ? originalEncoding : calculateCharacterEncoding(context); + + if (encoding != null && context.getExternalContext().getSession(false) != null) { + context.getExternalContext().getSessionMap().put(CHARACTER_ENCODING_KEY, encoding); + } + + if (originalEncoding != null) { return; } - encoding = calculateCharacterEncoding(context); if (encoding != null) { try { context.getExternalContext().setRequestCharacterEncoding(encoding); @@ -466,7 +471,7 @@ public String deriveLogicalViewId(FacesContext context, String requestViewId) { *

* Return a Jakarta Faces action URL derived from the viewId argument that is suitable to be used by * the {@link NavigationHandler} to issue a redirect request to the URL using a NonFaces request. Compliant - * implementations must implement this method as specified in + * implementations must implement this method as specified in * section 7.6.2 "Default ViewHandler Implementation" of the Jakarta Faces Specification Document. * The default implementation simply calls * through to {@link #getActionURL}, passing the arguments context and viewId. diff --git a/impl/src/main/java/jakarta/faces/component/UIData.java b/impl/src/main/java/jakarta/faces/component/UIData.java index 9c38a0338b..16d8dfc235 100644 --- a/impl/src/main/java/jakarta/faces/component/UIData.java +++ b/impl/src/main/java/jakarta/faces/component/UIData.java @@ -1013,8 +1013,14 @@ public void broadcast(FacesEvent event) throws AbortProcessingException { if (isNestedWithinIterator(context)) { setDataModel(null); } - int oldRowIndex = getRowIndex(); - setRowIndex(revent.getRowIndex()); + int currentRowIndex = getRowIndex(); + int broadcastedRowIndex = revent.getRowIndex(); + boolean needsToSetIndex = currentRowIndex != -1 || broadcastedRowIndex != -1; // #5213 + + if (needsToSetIndex) { + setRowIndex(broadcastedRowIndex); + } + FacesEvent rowEvent = revent.getFacesEvent(); UIComponent source = rowEvent.getComponent(); UIComponent compositeParent = null; @@ -1033,8 +1039,10 @@ public void broadcast(FacesEvent event) throws AbortProcessingException { compositeParent.popComponentFromEL(context); } } - setRowIndex(oldRowIndex); + if (needsToSetIndex) { + setRowIndex(currentRowIndex); + } } /** diff --git a/impl/src/main/java/jakarta/faces/component/UIInput.java b/impl/src/main/java/jakarta/faces/component/UIInput.java index ad8ad72001..92f84fd447 100644 --- a/impl/src/main/java/jakarta/faces/component/UIInput.java +++ b/impl/src/main/java/jakarta/faces/component/UIInput.java @@ -252,7 +252,7 @@ public String getFamily() { */ @Override public Object getSubmittedValue() { - if (submittedValue == null && !isValid() && considerEmptyStringNull(FacesContext.getCurrentInstance())) { // JAVASERVERFACES_SPEC_PUBLIC-671 + if (submittedValue == null && !isValid() && !isLocalValueSet() && considerEmptyStringNull(FacesContext.getCurrentInstance())) { // JAVASERVERFACES_SPEC_PUBLIC-671 return ""; } else { return submittedValue; diff --git a/impl/src/main/java/jakarta/faces/context/PartialResponseWriter.java b/impl/src/main/java/jakarta/faces/context/PartialResponseWriter.java index aa4b4cf893..49a6427519 100644 --- a/impl/src/main/java/jakarta/faces/context/PartialResponseWriter.java +++ b/impl/src/main/java/jakarta/faces/context/PartialResponseWriter.java @@ -17,8 +17,11 @@ package jakarta.faces.context; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.util.Map; +import com.sun.faces.RIConstants; + import jakarta.faces.component.NamingContainer; import jakarta.faces.component.UIViewRoot; import jakarta.faces.render.ResponseStateManager; @@ -97,7 +100,7 @@ public void startDocument() throws IOException { ResponseWriter writer = getWrapped(); String encoding = writer.getCharacterEncoding(); if (encoding == null) { - encoding = "utf-8"; + encoding = RIConstants.CHAR_ENCODING; } writer.writePreamble("\n"); writer.startElement("partial-response", null); diff --git a/impl/src/main/resources/com/sun/faces/LogStrings.properties b/impl/src/main/resources/com/sun/faces/LogStrings.properties index b318c6541c..cc78b3db42 100644 --- a/impl/src/main/resources/com/sun/faces/LogStrings.properties +++ b/impl/src/main/resources/com/sun/faces/LogStrings.properties @@ -25,7 +25,7 @@ faces.util.no.adapter.ctor.available="JSF1016: Target class ''{0}'' doesn't have # core tags faces.core.tags.eval_result_not_expected_type=JSF1011: Evaluation of expression for attribute ''{0}'' resulted in unexpected type. Expected {1}, but received {2}. faces.util_no_annotation_processed=JSF1014: Unable to load annotation class ''{0}''. As a result, this annotation will not be processed. -faces.config.listener.version=Initializing Mojarra ${mojarra.logstrings.version} for context ''{0}'' +faces.config.listener.version=Initializing Mojarra {0} for context ''{1}'' faces.config.listener.version.complete=Completed initializing Mojarra (${mojarra.logstrings.version}) for context ''{0}'' faces.config.listener.predestroy.error=JSF1017: ''{0}'' was thrown while handling the PreDestroy annotation(s) for bean ''{1}'' in scope ''{2}''. Message: {3} faces.viewhandler.requestpath.recursion=JSF1015: Request path ''{0}'' begins with one or more occurrences of the FacesServlet prefix path mapping ''{1}''. diff --git a/impl/src/test/java/com/sun/faces/config/processor/FacesConfigNamespaceContextTest.java b/impl/src/test/java/com/sun/faces/config/processor/FacesConfigNamespaceContextTest.java index d4d1150b59..cb59988b72 100644 --- a/impl/src/test/java/com/sun/faces/config/processor/FacesConfigNamespaceContextTest.java +++ b/impl/src/test/java/com/sun/faces/config/processor/FacesConfigNamespaceContextTest.java @@ -16,6 +16,7 @@ package com.sun.faces.config.processor; +import static com.sun.faces.util.Util.createLocalDocumentBuilderFactory; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; @@ -79,7 +80,7 @@ public void testJakartaEENSWithParameter() throws ParserConfigurationException, private Document createFacesConfig(String flowName, String namespace, String version) throws ParserConfigurationException { - DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); + DocumentBuilderFactory documentBuilderFactory = createLocalDocumentBuilderFactory(); documentBuilderFactory.setNamespaceAware(true); DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); Document docFlowConfig = documentBuilder.newDocument(); diff --git a/impl/src/test/java/jakarta/faces/FacesConfigOrderingTestCase.java b/impl/src/test/java/jakarta/faces/FacesConfigOrderingTestCase.java index c5c7187bfb..c072c4fd98 100644 --- a/impl/src/test/java/jakarta/faces/FacesConfigOrderingTestCase.java +++ b/impl/src/test/java/jakarta/faces/FacesConfigOrderingTestCase.java @@ -16,6 +16,8 @@ package jakarta.faces; +import static com.sun.faces.util.Util.createLocalDocumentBuilderFactory; + import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; @@ -25,10 +27,6 @@ import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; -import junit.framework.Test; -import junit.framework.TestCase; -import junit.framework.TestSuite; - import org.w3c.dom.Document; import org.w3c.dom.Element; @@ -36,6 +34,9 @@ import com.sun.faces.config.manager.documents.DocumentOrderingWrapper; import jakarta.faces.context.FacesContext; +import junit.framework.Test; +import junit.framework.TestCase; +import junit.framework.TestSuite; public class FacesConfigOrderingTestCase extends TestCase { @@ -391,7 +392,7 @@ private void populateIds(String elementName, List ids, String ns, private Document newDocument() throws ParserConfigurationException { - DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + DocumentBuilderFactory factory = createLocalDocumentBuilderFactory(); factory.setValidating(false); factory.setNamespaceAware(true); return factory.newDocumentBuilder().newDocument();