All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.sun.faces.application.view.MultiViewHandler Maven / Gradle / Ivy

Go to download

Jakarta Faces defines an MVC framework for building user interfaces for web applications, including UI components, state management, event handing, input validation, page navigation, and support for internationalization and accessibility.

There is a newer version: 4.1.2
Show newest version
/*
 * Copyright (c) 2022, 2023 Contributors to Eclipse Foundation.
 * Copyright (c) 1997, 2021 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.application.view;

import static com.sun.faces.RIConstants.SAVESTATE_FIELD_MARKER;
import static com.sun.faces.renderkit.RenderKitUtils.getResponseStateManager;
import static com.sun.faces.renderkit.RenderKitUtils.PredefinedPostbackParameter.RENDER_KIT_ID_PARAM;
import static com.sun.faces.util.MessageUtils.ILLEGAL_VIEW_ID_ID;
import static com.sun.faces.util.MessageUtils.getExceptionMessageString;
import static com.sun.faces.util.Util.getFacesMapping;
import static com.sun.faces.util.Util.getFirstWildCardMappingToFacesServlet;
import static com.sun.faces.util.Util.getViewHandler;
import static com.sun.faces.util.Util.isViewIdExactMappedToFacesServlet;
import static com.sun.faces.util.Util.notNull;
import static jakarta.faces.FactoryFinder.VIEW_DECLARATION_LANGUAGE_FACTORY;
import static jakarta.faces.push.PushContext.URI_PREFIX;
import static jakarta.faces.render.RenderKitFactory.HTML_BASIC_RENDER_KIT;
import static jakarta.faces.render.ResponseStateManager.NON_POSTBACK_VIEW_TOKEN_PARAM;
import static jakarta.servlet.http.MappingMatch.EXACT;
import static jakarta.servlet.http.MappingMatch.EXTENSION;
import static jakarta.servlet.http.MappingMatch.PATH;
import static java.util.Collections.unmodifiableSet;
import static java.util.Objects.requireNonNull;
import static java.util.logging.Level.SEVERE;
import static java.util.logging.Level.WARNING;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.logging.Logger;
import java.util.stream.Stream;

import com.sun.faces.config.WebConfiguration;
import com.sun.faces.util.FacesLogger;
import com.sun.faces.util.Util;

import jakarta.faces.FacesException;
import jakarta.faces.FactoryFinder;
import jakarta.faces.application.ViewHandler;
import jakarta.faces.application.ViewVisitOption;
import jakarta.faces.component.UIViewParameter;
import jakarta.faces.component.UIViewRoot;
import jakarta.faces.context.ExternalContext;
import jakarta.faces.context.FacesContext;
import jakarta.faces.view.ViewDeclarationLanguage;
import jakarta.faces.view.ViewDeclarationLanguageFactory;
import jakarta.faces.view.ViewMetadata;
import jakarta.servlet.http.HttpServletMapping;
import jakarta.servlet.http.HttpServletResponse;

/**
 * This {@link ViewHandler} implementation handles the Facelets VDL-based views.
 */
public class MultiViewHandler extends ViewHandler {

    // Log instance for this class
    private static final Logger LOGGER = FacesLogger.APPLICATION.getLogger();

    private final List configuredExtensions;
    private final Set protectedViews;

    private final ViewDeclarationLanguageFactory vdlFactory;

    // ------------------------------------------------------------ Constructors

    public MultiViewHandler() {
        WebConfiguration config = WebConfiguration.getInstance();

        configuredExtensions = config.getConfiguredExtensions();
        vdlFactory = (ViewDeclarationLanguageFactory) FactoryFinder.getFactory(VIEW_DECLARATION_LANGUAGE_FACTORY);
        protectedViews = new CopyOnWriteArraySet<>();
    }

    // ------------------------------------------------ Methods from ViewHandler

    /**
     * Call the default implementation of
     * {@link jakarta.faces.application.ViewHandler#initView(jakarta.faces.context.FacesContext)}
     *
     * @see jakarta.faces.application.ViewHandler#initView(jakarta.faces.context.FacesContext)
     */
    @Override
    public void initView(FacesContext context) throws FacesException {
        super.initView(context);
    }

    /**
     * 

* Call {@link ViewDeclarationLanguage#restoreView(jakarta.faces.context.FacesContext, String)}. *

* * @see ViewHandler#restoreView(jakarta.faces.context.FacesContext, String) */ @Override public UIViewRoot restoreView(FacesContext context, String viewId) { notNull("context", context); String physicalViewId = derivePhysicalViewId(context, viewId, false); return vdlFactory.getViewDeclarationLanguage(physicalViewId).restoreView(context, physicalViewId); } /** *

* Derive the physical view ID (i.e. the physical resource) and call call * {@link ViewDeclarationLanguage#createView(jakarta.faces.context.FacesContext, String)}. *

* * @see ViewHandler#restoreView(jakarta.faces.context.FacesContext, String) */ @Override public UIViewRoot createView(FacesContext context, String viewId) { notNull("context", context); String physicalViewId = derivePhysicalViewId(context, viewId, false); return vdlFactory.getViewDeclarationLanguage(physicalViewId).createView(context, physicalViewId); } /** *

* Call * {@link ViewDeclarationLanguage#renderView(jakarta.faces.context.FacesContext, jakarta.faces.component.UIViewRoot)} if * the view can be rendered. *

* * @see ViewHandler#renderView(jakarta.faces.context.FacesContext, jakarta.faces.component.UIViewRoot) */ @Override public void renderView(FacesContext context, UIViewRoot viewToRender) throws IOException, FacesException { notNull("context", context); notNull("viewToRender", viewToRender); vdlFactory.getViewDeclarationLanguage(viewToRender.getViewId()).renderView(context, viewToRender); } /** *

* This code is currently common to all {@link ViewHandlingStrategy} instances. *

* * @see ViewHandler#calculateLocale(jakarta.faces.context.FacesContext) */ @Override public Locale calculateLocale(FacesContext context) { notNull("context", context); Locale result = null; // Determine the locales that are acceptable to the client based on the // Accept-Language header and the find the best match among the // supported locales specified by the client. Iterator locales = context.getExternalContext().getRequestLocales(); while (locales.hasNext()) { Locale perf = locales.next(); result = findMatch(context, perf); if (result != null) { break; } } // no match is found. if (result == null) { if (context.getApplication().getDefaultLocale() == null) { result = Locale.getDefault(); } else { result = context.getApplication().getDefaultLocale(); } } return result; } /** *

* This code is currently common to all {@link ViewHandlingStrategy} instances. *

* * @see ViewHandler#calculateRenderKitId(jakarta.faces.context.FacesContext) */ @Override public String calculateRenderKitId(FacesContext context) { notNull("context", context); String result = RENDER_KIT_ID_PARAM.getValue(context); if (result == null) { if (null == (result = context.getApplication().getDefaultRenderKitId())) { result = HTML_BASIC_RENDER_KIT; } } return result; } /** *

* This code is currently common to all {@link ViewHandlingStrategy} instances. *

* * @see ViewHandler#writeState(jakarta.faces.context.FacesContext) */ @Override public void writeState(FacesContext context) throws IOException { notNull("context", context); if (!context.getPartialViewContext().isAjaxRequest()) { LOGGER.fine(() -> "Begin writing marker for viewId " + context.getViewRoot().getViewId()); WriteBehindStateWriter writer = WriteBehindStateWriter.getCurrentInstance(); if (writer != null) { writer.writingState(); } context.getResponseWriter().write(SAVESTATE_FIELD_MARKER); LOGGER.fine(() -> "End writing marker for viewId " + context.getViewRoot().getViewId()); } } /** *

* This code is currently common to all {@link ViewHandlingStrategy} instances. *

* * @see ViewHandler#getActionURL(jakarta.faces.context.FacesContext, String) */ @Override public String getActionURL(FacesContext context, String viewId) { String result = getActionURLWithoutViewProtection(context, viewId); // http://java.net/jira/browse/JAVASERVERFACES-2204 // PENDING: this code is optimized to be fast to write. // It must be optimized to be fast to run. // See git clone ssh://[email protected]/grizzly~git 1_9_36 for // how grizzly does this. ViewHandler viewHandler = context.getApplication().getViewHandler(); Set urlPatterns = viewHandler.getProtectedViewsUnmodifiable(); // Implement section 12.1 of the Servlet spec. if (urlPatterns.contains(viewId)) { StringBuilder builder = new StringBuilder(result); // If the result already has a query string... if (result.contains("?")) { // ...assume it also has one or more parameters, and // append an additional parameter. builder.append("&"); } else { // Otherwise, this is the first parameter in the result. builder.append("?"); } String tokenValue = getResponseStateManager(context, viewHandler.calculateRenderKitId(context)) .getCryptographicallyStrongTokenFromSession(context); builder.append(NON_POSTBACK_VIEW_TOKEN_PARAM).append("=").append(tokenValue); result = builder.toString(); } return result; } /** *

* This code is currently common to all {@link ViewHandlingStrategy} instances. *

* * @see ViewHandler#getResourceURL(jakarta.faces.context.FacesContext, String) */ @Override public String getResourceURL(FacesContext context, String path) { requireNonNull(context, "context"); requireNonNull(path, "path"); if (path.charAt(0) == '/') { return context.getExternalContext().getRequestContextPath() + path; } return path; } @Override public String getWebsocketURL(FacesContext context, String channel) { requireNonNull(context, "context"); requireNonNull(channel, "channel"); ExternalContext externalContext = context.getExternalContext(); return externalContext.encodeWebsocketURL(externalContext.getRequestContextPath() + URI_PREFIX + "/" + channel); } @Override public String getBookmarkableURL(FacesContext context, String viewId, Map> parameters, boolean includeViewParams) { Map> params; if (includeViewParams) { params = getFullParameterList(context, viewId, parameters); } else { params = parameters; } ExternalContext ectx = context.getExternalContext(); return ectx.encodeActionURL(ectx.encodeBookmarkableURL(getViewHandler(context).getActionURL(context, viewId), params)); } @Override public void addProtectedView(String urlPattern) { protectedViews.add(urlPattern); } @Override public Set getProtectedViewsUnmodifiable() { return unmodifiableSet(protectedViews); } @Override public boolean removeProtectedView(String urlPattern) { return protectedViews.remove(urlPattern); } /** * @see ViewHandler#getRedirectURL(jakarta.faces.context.FacesContext, String, java.util.Map, boolean) */ @Override public String getRedirectURL(FacesContext context, String viewId, Map> parameters, boolean includeViewParams) { String responseEncoding = Util.getResponseEncoding(context); if (parameters != null) { Map> decodedParameters = new HashMap<>(); for (Map.Entry> entry : parameters.entrySet()) { String string = entry.getKey(); List list = entry.getValue(); List values = new ArrayList<>(); for (Iterator it = list.iterator(); it.hasNext();) { String value = it.next(); try { value = URLDecoder.decode(value, responseEncoding); } catch (UnsupportedEncodingException e) { throw new RuntimeException("Unable to decode"); } values.add(value); } decodedParameters.put(string, values); } parameters = decodedParameters; } Map> params; if (includeViewParams) { params = getFullParameterList(context, viewId, parameters); } else { params = parameters; } ExternalContext ectx = context.getExternalContext(); return ectx.encodeActionURL(ectx.encodeRedirectURL(Util.getViewHandler(context).getActionURL(context, viewId), params)); } /** * @see ViewHandler#getViewDeclarationLanguage(jakarta.faces.context.FacesContext, String) */ @Override public ViewDeclarationLanguage getViewDeclarationLanguage(FacesContext context, String viewId) { return vdlFactory.getViewDeclarationLanguage(viewId); } @Override public Stream getViews(FacesContext context, String path, ViewVisitOption... options) { return vdlFactory.getAllViewDeclarationLanguages().stream().flatMap(vdl -> vdl.getViews(context, path, options)); } @Override public Stream getViews(FacesContext context, String path, int maxDepth, ViewVisitOption... options) { return vdlFactory.getAllViewDeclarationLanguages().stream().flatMap(vdl -> vdl.getViews(context, path, maxDepth, options)); } @Override public String deriveViewId(FacesContext context, String requestViewId) { return derivePhysicalViewId(context, requestViewId, true); } @Override public String deriveLogicalViewId(FacesContext context, String requestViewId) { return derivePhysicalViewId(context, requestViewId, false); } // ------------------------------------------------------- Protected Methods protected String derivePhysicalViewId(FacesContext ctx, String requestViewId, boolean checkPhysical) { if (requestViewId == null) { return null; } HttpServletMapping mapping = getFacesMapping(ctx); String physicalViewId; if (mapping.getMappingMatch() == EXTENSION) { // Suffix mapping, e.g. /foo.xhtml physicalViewId = convertViewId(ctx, requestViewId); } else if (mapping.getMappingMatch() == EXACT) { if (requestViewId.equals(mapping.getPattern())) { // Fuzzy logic: if request equals the view ID we're asking for // this is a call from MultiViewHandler.createView. In that case instead // of /foo we want /foo.xhtml. return convertViewId(ctx, requestViewId); } // Exact mapping, e.g. /foo // We're likely called here by derive*ViewId, which wants /foo physicalViewId = requestViewId; } else { // Prefix mapping, e.g. /faces/foo.xhtml physicalViewId = normalizeRequestURI(requestViewId, mapping.getPattern().replace("/*", "")); } if (checkPhysical && !getViewDeclarationLanguage(ctx, physicalViewId).viewExists(ctx, physicalViewId)) { return null; } return physicalViewId; } /** *

* If the specified mapping is a prefix mapping, and the provided request URI (sometimes the value from * ExternalContext.getRequestServletPath()) starts with mapping, prune the mapping from * the URI and return it, otherwise, return the original URI. * * @param viewId something resembling a view id, can come from the request or from the navigation handler. * @param mapping the FacesServlet mapping used for this request with the "/*" removed, e.g. /faces instead of /faces/* * @return the viewId without additional prefix FacesServlet mappings * * @since 1.2 */ protected String normalizeRequestURI(String viewId, String mapping) { boolean logged = false; while (viewId.startsWith(mapping)) { if (!logged && LOGGER.isLoggable(WARNING)) { logged = true; LOGGER.log(WARNING, "faces.viewhandler.requestpath.recursion", new Object[] { viewId, mapping }); } viewId = viewId.substring(mapping.length()); } return viewId; } /** *

* Adjust the viewID per the requirements of {@link #renderView}. *

* * @param context current {@link jakarta.faces.context.FacesContext} * @param viewId incoming view ID * @return the view ID with an altered suffix mapping (if necessary) */ protected String convertViewId(FacesContext context, String viewId) { // if the viewId doesn't already use the above suffix, // replace or append. int extIdx = viewId.lastIndexOf('.'); int length = viewId.length(); StringBuilder buffer = new StringBuilder(length); for (String ext : configuredExtensions) { if (viewId.endsWith(ext)) { return viewId; } appendOrReplaceExtension(viewId, ext, length, extIdx, buffer); return buffer.toString(); } return viewId; } protected Map> getFullParameterList(FacesContext ctx, String viewId, Map> existingParameters) { Map> copy; if (existingParameters == null || existingParameters.isEmpty()) { copy = new LinkedHashMap<>(4); } else { copy = new LinkedHashMap<>(existingParameters); } addViewParameters(ctx, viewId, copy); return copy; } protected void addViewParameters(FacesContext ctx, String viewId, Map> existingParameters) { UIViewRoot currentRoot = ctx.getViewRoot(); String currentViewId = currentRoot.getViewId(); Collection toViewParams = Collections.emptyList(); Collection currentViewParams; boolean currentIsSameAsNew = false; currentViewParams = ViewMetadata.getViewParameters(currentRoot); if (currentViewId.equals(viewId)) { currentIsSameAsNew = true; toViewParams = currentViewParams; } else { ViewMetadata viewMetadata = getViewDeclarationLanguage(ctx, viewId).getViewMetadata(ctx, viewId); if (viewMetadata != null) { UIViewRoot root = viewMetadata.createMetadataView(ctx); toViewParams = ViewMetadata.getViewParameters(root); } } if (toViewParams.isEmpty()) { return; } for (UIViewParameter viewParam : toViewParams) { String value = null; // don't bother looking at view parameter if it's been overridden if (existingParameters.containsKey(viewParam.getName())) { continue; } if (paramHasValueExpression(viewParam)) { value = viewParam.getStringValueFromModel(ctx); } if (value == null) { if (currentIsSameAsNew) { /* * Anonymous view parameter: get string value from UIViewParameter instance stored in current view. */ value = viewParam.getStringValue(ctx); } else { /* * Or transfer string value from matching UIViewParameter instance stored in current view. */ value = getStringValueToTransfer(ctx, viewParam, currentViewParams); } } if (value != null) { List existing = existingParameters.computeIfAbsent(viewParam.getName(), k -> new ArrayList<>(4)); existing.add(value); } } } /** * Attempts to find a matching locale based on pref and list of supported locales, using the matching * algorithm as described in JSTL 8.3.2. * * @param context the FacesContext for the current request * @param pref the preferred locale * @return the Locale based on pref and the matching alogritm specified in JSTL 8.3.2 */ protected Locale findMatch(FacesContext context, Locale pref) { Locale result = null; Iterator it = context.getApplication().getSupportedLocales(); while (it.hasNext()) { Locale supportedLocale = it.next(); if (pref.equals(supportedLocale)) { // exact match result = supportedLocale; break; } else { // Make sure the preferred locale doesn't have country // set, when doing a language match, For ex., if the // preferred locale is "en-US", if one of supported // locales is "en-UK", even though its language matches // that of the preferred locale, we must ignore it. if (pref.getLanguage().equals(supportedLocale.getLanguage()) && supportedLocale.getCountry().length() == 0) { result = supportedLocale; } } } // if it's not in the supported locales, if (result == null) { Locale defaultLocale = context.getApplication().getDefaultLocale(); if (defaultLocale != null) { if (pref.equals(defaultLocale)) { // exact match result = defaultLocale; } else { // Make sure the preferred locale doesn't have country // set, when doing a language match, For ex., if the // preferred locale is "en-US", if one of supported // locales is "en-UK", even though its language matches // that of the preferred locale, we must ignore it. if (pref.getLanguage().equals(defaultLocale.getLanguage()) && defaultLocale.getCountry().length() == 0) { result = defaultLocale; } } } } return result; } /** *

* Send {@link HttpServletResponse#SC_NOT_FOUND} (404) to the client. *

* * @param context the {@link FacesContext} for the current request */ protected void send404Error(FacesContext context) { try { context.responseComplete(); context.getExternalContext().responseSendError(HttpServletResponse.SC_NOT_FOUND, ""); } catch (IOException ioe) { throw new FacesException(ioe); } } // --------------------------------------------------------- Private Methods private String getActionURLWithoutViewProtection(FacesContext context, String viewId) { notNull("context", context); notNull("viewId", viewId); if (viewId.length() == 0 || viewId.charAt(0) != '/') { LOGGER.log(SEVERE, "faces.illegal_view_id_error", viewId); throw new IllegalArgumentException(getExceptionMessageString(ILLEGAL_VIEW_ID_ID, viewId)); } // Acquire the context path, which we will prefix on all results String contextPath = context.getExternalContext().getRequestContextPath(); // Acquire the mapping used to execute this request HttpServletMapping mapping = getFacesMapping(context); // ### Deal with exact mapping if (mapping.getMappingMatch() == EXACT) { if (viewId.contains(".")) { for (String extension : configuredExtensions) { if (viewId.endsWith(extension)) { String exactViewId = viewId.substring(0, viewId.lastIndexOf(extension)); if (isViewIdExactMappedToFacesServlet(exactViewId)) { return contextPath + exactViewId; } } } } else { if (isViewIdExactMappedToFacesServlet(viewId)) { return contextPath + viewId; } } // No exact mapping for the requested view id, see if Facelets service is mapped to // e.g. /faces/* or *.xhtml and take that mapping mapping = getFirstWildCardMappingToFacesServlet(context.getExternalContext()); if (mapping == null) { // If there are only exact mappings and the view is not exact mapped, // we can't serve this view throw new IllegalStateException("No suitable mapping for FacesServlet found. To serve views that are not exact mapped " + "FacesServlet should have at least one prefix or suffix mapping."); } } // ### Deal with prefix/path mapping, e.g. /faces/* if (mapping.getMappingMatch() == PATH) { return contextPath + mapping.getPattern().replace("/*", viewId); } // ### Deal with suffix/extension mapping, e.g. *.xhtml // Check for case where viewId has no extension (e.g. /foo) if (!viewId.contains(".")) { // Just add the mapping extension to it and return return contextPath + mapping.getPattern().replace("*", viewId); } // Remove the * in the pattern, e.g. *.xhtml -> .xhtml String mappingExtension = mapping.getPattern().replace("*", ""); // Check for the case viewId already has exactly the mapping extension (e.g. /foo.xhtml) if (viewId.endsWith(mappingExtension)) { // Just return it directly return contextPath + viewId; } // Replace whatever extension the viewId has (e.g. /foo.doc) with the mapping extension return contextPath + viewId.substring(0, viewId.lastIndexOf('.')) + mappingExtension; } private static boolean paramHasValueExpression(UIViewParameter param) { return param.getValueExpression("value") != null; } private static String getStringValueToTransfer(FacesContext context, UIViewParameter param, Collection viewParams) { if (viewParams != null && !viewParams.isEmpty()) { for (UIViewParameter candidate : viewParams) { if (candidate.getName() != null && param.getName() != null && candidate.getName().equals(param.getName())) { return candidate.getStringValue(context); } } } return param.getStringValue(context); } // Utility method used by viewId conversion. Appends the extension // if no extension is present. Otherwise, replaces the extension. private void appendOrReplaceExtension(String viewId, String extension, int length, int extensionIndex, StringBuilder buffer) { buffer.setLength(0); buffer.append(viewId); if (extensionIndex != -1) { buffer.replace(extensionIndex, length, extension); } else { // no extension in the provided viewId, append the suffix buffer.append(extension); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy