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

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

The 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 - 2024 Weber Informatics LLC | Privacy Policy