/* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright (c) 1997-2013 Oracle and/or its affiliates. All rights reserved. * * The contents of this file are subject to the terms of either the GNU * General Public License Version 2 only ("GPL") or the Common Development * and Distribution License("CDDL") (collectively, the "License"). You * may not use this file except in compliance with the License. You can * obtain a copy of the License at * https://glassfish.java.net/public/CDDL+GPL_1_1.html * or packager/legal/LICENSE.txt. See the License for the specific * language governing permissions and limitations under the License. * * When distributing the software, include this License Header Notice in each * file and include the License file at packager/legal/LICENSE.txt. * * GPL Classpath Exception: * Oracle designates this particular file as subject to the "Classpath" * exception as provided by Oracle in the GPL Version 2 section of the License * file that accompanied this code. * * Modifications: * If applicable, add the following below the License Header, with the fields * enclosed by brackets [] replaced by your own identifying information: * "Portions Copyright [year] [name of copyright owner]" * * Contributor(s): * If you wish your version of this file to be governed by only the CDDL or * only the GPL Version 2, indicate your decision by adding "[Contributor] * elects to include this software in this distribution under the [CDDL or GPL * Version 2] license." If you don't indicate a single choice of license, a * recipient has the option to distribute your version of this file under * either the CDDL, the GPL Version 2 or to extend the choice of license to * its licensees as provided above. However, if you add GPL Version 2 code * and therefore, elected the GPL Version 2 license, then the option applies * only if the new code is made subject to such option by the copyright * holder. */ package com.sun.faces.application.view; import static com.sun.faces.RIConstants.DYNAMIC_COMPONENT; import static com.sun.faces.RIConstants.FACELETS_ENCODING_KEY; import static com.sun.faces.RIConstants.FLOW_DEFINITION_ID_SUFFIX; import static com.sun.faces.config.WebConfiguration.WebContextInitParameter.FaceletsBufferSize; import static com.sun.faces.config.WebConfiguration.WebContextInitParameter.FaceletsViewMappings; import static com.sun.faces.config.WebConfiguration.WebContextInitParameter.StateSavingMethod; import static com.sun.faces.context.StateContext.getStateContext; import static com.sun.faces.util.ComponentStruct.ADD; import static com.sun.faces.util.ComponentStruct.REMOVE; import static com.sun.faces.util.RequestStateManager.FACELET_FACTORY; import static com.sun.faces.util.Util.getDOCTYPEFromFacesContextAttributes; import static com.sun.faces.util.Util.getXMLDECLFromFacesContextAttributes; import static com.sun.faces.util.Util.isEmpty; import static com.sun.faces.util.Util.isViewIdExactMappedToFacesServlet; import static com.sun.faces.util.Util.isViewPopulated; import static com.sun.faces.util.Util.notNull; import static com.sun.faces.util.Util.saveDOCTYPEToFacesContextAttributes; import static com.sun.faces.util.Util.saveXMLDECLToFacesContextAttributes; import static com.sun.faces.util.Util.setViewPopulated; import static com.sun.faces.util.Util.split; import static java.lang.Boolean.TRUE; import static java.util.Arrays.stream; import static java.util.Collections.emptyList; import static java.util.logging.Level.FINE; import static java.util.logging.Level.FINEST; import static java.util.logging.Level.SEVERE; import static java.util.logging.Level.WARNING; import static javax.faces.FactoryFinder.VIEW_DECLARATION_LANGUAGE_FACTORY; import static javax.faces.application.ProjectStage.Development; import static javax.faces.application.Resource.COMPONENT_RESOURCE_KEY; import static javax.faces.application.StateManager.IS_BUILDING_INITIAL_STATE; import static javax.faces.application.StateManager.STATE_SAVING_METHOD_SERVER; import static javax.faces.application.ViewHandler.CHARACTER_ENCODING_KEY; import static javax.faces.application.ViewHandler.DEFAULT_FACELETS_SUFFIX; import static javax.faces.application.ViewVisitOption.RETURN_AS_MINIMAL_IMPLICIT_OUTCOME; import static javax.faces.component.UIComponent.BEANINFO_KEY; import static javax.faces.component.UIComponent.COMPOSITE_FACET_NAME; import static javax.faces.component.UIComponent.VIEW_LOCATION_KEY; import static javax.faces.component.UIViewRoot.COMPONENT_TYPE; import static javax.faces.view.AttachedObjectTarget.ATTACHED_OBJECT_TARGETS_KEY; import static javax.faces.view.facelets.FaceletContext.FACELET_CONTEXT_KEY; import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND; import java.beans.BeanDescriptor; import java.beans.BeanInfo; import java.beans.PropertyDescriptor; import java.io.FileNotFoundException; import java.io.IOException; import java.io.Writer; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; import java.util.logging.Level; import java.util.logging.Logger; import java.util.stream.Stream; import javax.el.ELContext; import javax.el.ExpressionFactory; import javax.el.MethodExpression; import javax.el.ValueExpression; import javax.el.VariableMapper; import javax.faces.FacesException; import javax.faces.FactoryFinder; import javax.faces.application.Resource; import javax.faces.application.StateManager; import javax.faces.application.ViewHandler; import javax.faces.application.ViewVisitOption; import javax.faces.component.ActionSource2; import javax.faces.component.ContextCallback; import javax.faces.component.EditableValueHolder; import javax.faces.component.UIComponent; import javax.faces.component.UIPanel; import javax.faces.component.UIViewRoot; import javax.faces.component.visit.VisitCallback; import javax.faces.component.visit.VisitContext; import javax.faces.component.visit.VisitResult; import javax.faces.context.ExternalContext; import javax.faces.context.FacesContext; import javax.faces.context.ResponseWriter; import javax.faces.event.ActionEvent; import javax.faces.event.MethodExpressionActionListener; import javax.faces.event.MethodExpressionValueChangeListener; import javax.faces.event.PostAddToViewEvent; import javax.faces.event.ValueChangeEvent; import javax.faces.render.RenderKit; import javax.faces.render.ResponseStateManager; import javax.faces.validator.MethodExpressionValidator; import javax.faces.view.ActionSource2AttachedObjectHandler; import javax.faces.view.ActionSource2AttachedObjectTarget; import javax.faces.view.AttachedObjectHandler; import javax.faces.view.AttachedObjectTarget; import javax.faces.view.BehaviorHolderAttachedObjectHandler; import javax.faces.view.BehaviorHolderAttachedObjectTarget; import javax.faces.view.EditableValueHolderAttachedObjectHandler; import javax.faces.view.EditableValueHolderAttachedObjectTarget; import javax.faces.view.StateManagementStrategy; import javax.faces.view.ValueHolderAttachedObjectHandler; import javax.faces.view.ValueHolderAttachedObjectTarget; import javax.faces.view.ViewDeclarationLanguage; import javax.faces.view.ViewDeclarationLanguageFactory; import javax.faces.view.ViewMetadata; import javax.faces.view.facelets.Facelet; import javax.faces.view.facelets.FaceletContext; import javax.servlet.http.HttpSession; import com.sun.faces.application.ApplicationAssociate; import com.sun.faces.config.WebConfiguration; import com.sun.faces.context.StateContext; import com.sun.faces.facelets.el.ContextualCompositeMethodExpression; import com.sun.faces.facelets.el.VariableMapperWrapper; import com.sun.faces.facelets.impl.DefaultFaceletFactory; import com.sun.faces.facelets.impl.XMLFrontMatterSaver; import com.sun.faces.facelets.tag.composite.CompositeComponentBeanInfo; import com.sun.faces.facelets.tag.jsf.CompositeComponentTagHandler; import com.sun.faces.facelets.tag.ui.UIDebug; import com.sun.faces.renderkit.RenderKitUtils; import com.sun.faces.util.Cache; import com.sun.faces.util.Cache.Factory; import com.sun.faces.util.ComponentStruct; import com.sun.faces.util.FacesLogger; import com.sun.faces.util.HtmlUtils; import com.sun.faces.util.RequestStateManager; import com.sun.faces.util.Util; /** * This {@link ViewHandlingStrategy} handles Facelets/PDL-based views. */ public class FaceletViewHandlingStrategy extends ViewHandlingStrategy { private static final Logger LOGGER = FacesLogger.APPLICATION.getLogger(); private ViewDeclarationLanguageFactory vdlFactory; private DefaultFaceletFactory faceletFactory; // Array of viewId extensions that should be handled by Facelets private String[] extensionsArray; // Array of viewId prefixes that should be handled by Facelets private String[] prefixesArray; public static final String IS_BUILDING_METADATA = FaceletViewHandlingStrategy.class.getName() + ".IS_BUILDING_METADATA"; public static final String RESOURCE_LIBRARY_CONTRACT_DATA_STRUCTURE_KEY = FaceletViewHandlingStrategy.class.getName() + ".RESOURCE_LIBRARY_CONTRACT_DATA_STRUCTURE"; private MethodRetargetHandlerManager retargetHandlerManager = new MethodRetargetHandlerManager(); private int responseBufferSize; private boolean responseBufferSizeSet; private boolean isTrinidadStateManager; private Cache<Resource, BeanInfo> metadataCache; private Map<String, List<String>> contractMappings; // ------------------------------------------------------------ Constructors public FaceletViewHandlingStrategy() { initialize(); } // ------------------------------------------------------------ Constructors public static boolean isBuildingMetadata(FacesContext context) { return context.getAttributes().containsKey(IS_BUILDING_METADATA); } // ------------------------------------ Methods from ViewDeclarationLanguage /** * <p> * If {@link UIDebug#debugRequest(javax.faces.context.FacesContext)}} is <code>true</code>, * simply return a new UIViewRoot(), otherwise, call the default logic. * </p> * @see ViewDeclarationLanguage#restoreView(javax.faces.context.FacesContext, java.lang.String) */ @Override public UIViewRoot restoreView(FacesContext context, String viewId) { notNull("context", context); notNull("viewId", viewId); if (UIDebug.debugRequest(context)) { context.getApplication().createComponent(COMPONENT_TYPE); } UIViewRoot viewRoot; /* * Check if we are stateless. */ ViewHandler outerViewHandler = context.getApplication().getViewHandler(); String renderKitId = outerViewHandler.calculateRenderKitId(context); ResponseStateManager rsm = RenderKitUtils.getResponseStateManager(context, renderKitId); if (rsm.isStateless(context, viewId)) { try { context.setProcessingEvents(true); ViewDeclarationLanguage vdl = vdlFactory.getViewDeclarationLanguage(viewId); viewRoot = vdl.createView(context, viewId); context.setViewRoot(viewRoot); vdl.buildView(context, viewRoot); if (!viewRoot.isTransient()) { throw new FacesException("Unable to restore view " + viewId); } return viewRoot; } catch (IOException ioe) { throw new FacesException(ioe); } } if (getStateContext(context).isPartialStateSaving(context, viewId)) { try { context.setProcessingEvents(false); ViewDeclarationLanguage vdl = vdlFactory.getViewDeclarationLanguage(viewId); viewRoot = vdl.getViewMetadata(context, viewId).createMetadataView(context); context.setViewRoot(viewRoot); outerViewHandler = context.getApplication().getViewHandler(); renderKitId = outerViewHandler.calculateRenderKitId(context); rsm = RenderKitUtils.getResponseStateManager(context, renderKitId); Object[] rawState = (Object[]) rsm.getState(context, viewId); if (rawState != null) { @SuppressWarnings("unchecked") Map<String, Object> state = (Map<String, Object>) rawState[1]; if (state != null) { String clientId = viewRoot.getClientId(context); Object stateObj = state.get(clientId); if (stateObj != null) { context.getAttributes().put("com.sun.faces.application.view.restoreViewScopeOnly", true); viewRoot.restoreState(context, stateObj); context.getAttributes().remove("com.sun.faces.application.view.restoreViewScopeOnly"); } } } context.setProcessingEvents(true); vdl.buildView(context, viewRoot); } catch (IOException ioe) { throw new FacesException(ioe); } } UIViewRoot root = super.restoreView(context, viewId); ViewHandler viewHandler = context.getApplication().getViewHandler(); ViewDeclarationLanguage vdl = viewHandler.getViewDeclarationLanguage(context, viewId); context.setResourceLibraryContracts(vdl.calculateResourceLibraryContracts(context, viewId)); StateContext stateCtx = StateContext.getStateContext(context); stateCtx.startTrackViewModifications(context, root); return root; } @Override public ViewMetadata getViewMetadata(FacesContext context, String viewId) { notNull("context", context); notNull("viewId", viewId); return new ViewMetadataImpl(viewId); } /** * @see ViewDeclarationLanguage#createView(javax.faces.context.FacesContext, java.lang.String) */ @Override public UIViewRoot createView(FacesContext ctx, String viewId) { notNull("context", ctx); notNull("viewId", viewId); if (UIDebug.debugRequest(ctx)) { UIViewRoot root = (UIViewRoot) ctx.getApplication().createComponent(COMPONENT_TYPE); root.setViewId(viewId); return root; } UIViewRoot result = super.createView(ctx, viewId); ViewHandler viewHandler = ctx.getApplication().getViewHandler(); ViewDeclarationLanguage vdl = viewHandler.getViewDeclarationLanguage(ctx, viewId); ctx.setResourceLibraryContracts(vdl.calculateResourceLibraryContracts(ctx, viewId)); return result; } /** * @see ViewDeclarationLanguage#buildView(FacesContext, UIViewRoot) */ @Override public void buildView(FacesContext ctx, UIViewRoot view) throws IOException { StateContext stateCtx = StateContext.getStateContext(ctx); if (isViewPopulated(ctx, view)) { Facelet facelet = faceletFactory.getFacelet(ctx, view.getViewId()); // Disable events from being intercepted by the StateContext by // virute of re-applying the handlers. try { stateCtx.setTrackViewModifications(false); facelet.apply(ctx, view); reapplyDynamicActions(ctx); if (stateCtx.isPartialStateSaving(ctx, view.getViewId())) { markInitialStateIfNotMarked(view); } } finally { stateCtx.setTrackViewModifications(true); } return; } view.setViewId(view.getViewId()); if (LOGGER.isLoggable(FINE)) { LOGGER.fine("Building View: " + view.getViewId()); } if (faceletFactory == null) { ApplicationAssociate associate = ApplicationAssociate.getInstance(ctx.getExternalContext()); faceletFactory = associate.getFaceletFactory(); assert (faceletFactory != null); } RequestStateManager.set(ctx, FACELET_FACTORY, faceletFactory); Facelet facelet = faceletFactory.getFacelet(ctx, view.getViewId()); // populate UIViewRoot try { ctx.getAttributes().put(IS_BUILDING_INITIAL_STATE, Boolean.TRUE); stateCtx.setTrackViewModifications(false); facelet.apply(ctx, view); if (facelet instanceof XMLFrontMatterSaver) { XMLFrontMatterSaver frontMatterSaver = (XMLFrontMatterSaver) facelet; String docType = frontMatterSaver.getSavedDoctype(); if (docType != null) { saveDOCTYPEToFacesContextAttributes(docType); } String XMLDECL = frontMatterSaver.getSavedXMLDecl(); if (XMLDECL != null) { saveXMLDECLToFacesContextAttributes(XMLDECL); } } if (!stateCtx.isPartialStateSaving(ctx, view.getViewId())) { reapplyDynamicActions(ctx); } doPostBuildActions(ctx, view); } finally { ctx.getAttributes().remove(IS_BUILDING_INITIAL_STATE); } ctx.getApplication().publishEvent(ctx, PostAddToViewEvent.class, UIViewRoot.class, view); markInitialState(ctx, view); setViewPopulated(ctx, view); } /** * @see javax.faces.view.ViewDeclarationLanguage#renderView(javax.faces.context.FacesContext, javax.faces.component.UIViewRoot) */ @Override public void renderView(FacesContext ctx, UIViewRoot viewToRender) throws IOException { // Suppress rendering if "rendered" property on the component is false if (!viewToRender.isRendered()) { return; } // Log request if (LOGGER.isLoggable(FINE)) { LOGGER.fine("Rendering View: " + viewToRender.getViewId()); } WriteBehindStateWriter stateWriter = null; try { // Only build the view if this view has not yet been built. if (!isViewPopulated(ctx, viewToRender)) { ViewDeclarationLanguage vdl = vdlFactory.getViewDeclarationLanguage(viewToRender.getViewId()); vdl.buildView(ctx, viewToRender); } // Setup writer and assign it to the ctx ResponseWriter origWriter = ctx.getResponseWriter(); if (origWriter == null) { origWriter = createResponseWriter(ctx); } ExternalContext extContext = ctx.getExternalContext(); /* * Make sure we have a session here if we are using server state * saving. The WriteBehindStateWriter needs an active session when * it writes out state to a server session. * * Note if you flag a view as transient then we won't acquire the * session as you are stating it does not need one. */ if (isServerStateSaving() && !viewToRender.isTransient()) { getSession(ctx); } Writer outputWriter = extContext.getResponseOutputWriter(); stateWriter = new WriteBehindStateWriter(outputWriter, ctx, responseBufferSize); ResponseWriter writer = origWriter.cloneWithWriter(stateWriter); ctx.setResponseWriter(writer); // Don't call startDoc and endDoc on a partial response if (ctx.getPartialViewContext().isPartialRequest()) { viewToRender.encodeAll(ctx); try { ctx.getExternalContext().getFlash().doPostPhaseActions(ctx); } catch (UnsupportedOperationException uoe) { if (LOGGER.isLoggable(FINE)) { LOGGER.fine("ExternalContext.getFlash() throw UnsupportedOperationException -> Flash unavailable"); } } } else { if (ctx.isProjectStage(Development)) { FormOmittedChecker.check(ctx); } // render the view to the response String xmlDecl = getXMLDECLFromFacesContextAttributes(ctx); if (null != xmlDecl) { // Do not escape. writer.writePreamble(xmlDecl); } String docType = getDOCTYPEFromFacesContextAttributes(ctx); if (null != docType) { // Do not escape. writer.writeDoctype(docType); } writer.startDocument(); viewToRender.encodeAll(ctx); try { ctx.getExternalContext().getFlash().doPostPhaseActions(ctx); } catch (UnsupportedOperationException uoe) { if (LOGGER.isLoggable(FINE)) { LOGGER.fine("ExternalContext.getFlash() throw UnsupportedOperationException -> Flash unavailable"); } } writer.endDocument(); } // Finish writing writer.close(); // Flush to origWriter if (stateWriter.stateWritten()) { stateWriter.flushToWriter(); } } catch (FileNotFoundException fnfe) { handleFaceletNotFound(ctx, viewToRender.getViewId(), fnfe.getMessage()); } catch (Exception e) { handleRenderException(ctx, e); } finally { if (stateWriter != null) { stateWriter.release(); } } } @Override public StateManagementStrategy getStateManagementStrategy(FacesContext context, String viewId) { StateManagementStrategy result; StateContext stateCtx = StateContext.getStateContext(context); if (stateCtx.isPartialStateSaving(context, viewId)) { result = new FaceletPartialStateManagementStrategy(context); } else { // Spec for this method says: // Implementations that provide the VDL for Facelets for JSF 2.0 // and later must return non-null from this method. // Limit the specification violating change to the case where // we are running in Trinidad. // result = isTrinidadStateManager ? null : new JspStateManagementStrategy(context); } return result; } /** * Called by Application._createComponent(Resource). * * This method creates two temporary UIComponent instances to aid in * the creation of the compcomp metadata. These instances no longer * needed after the method returns and can be safely garbage * collected. * * PENDING(): memory analysis should be done to verify there are no * memory leaks as a result of this implementation. * * The instances are * * 1. tmp: a javax.faces.NamingContainer to serve as the temporary * top level component * * 2. facetComponent: a javax.faces.Panel to serve as the parent * UIComponent that is passed to Facelets so that the <cc:interface> * section can be parsed and understood. * * Per the compcomp spec, tmp has the compcomp Resource stored in * its attr set under the key Resource.COMPONENT_RESOURCE_KEY. tmp * has the facetComponent added as its * UIComponent.COMPOSITE_FACET_NAME facet. * */ @Override public BeanInfo getComponentMetadata(FacesContext context, Resource ccResource) { DefaultFaceletFactory factory = (DefaultFaceletFactory) RequestStateManager.get(context, FACELET_FACTORY); DefaultFaceletFactory ourFactory = (DefaultFaceletFactory) factory; if (ourFactory.needsToBeRefreshed(ccResource.getURL())) { metadataCache.remove(ccResource); } return metadataCache.get(ccResource); } public BeanInfo createComponentMetadata(FacesContext context, Resource ccResource) { // PENDING this implementation is terribly wasteful. // Must find a better way. FaceletContext faceletContext = (FaceletContext) context.getAttributes().get(FACELET_CONTEXT_KEY); DefaultFaceletFactory factory = (DefaultFaceletFactory) RequestStateManager.get(context, FACELET_FACTORY); VariableMapper orig = faceletContext.getVariableMapper(); // Create tmp and facetComponent UIComponent tmp = context.getApplication().createComponent("javax.faces.NamingContainer"); UIPanel facetComponent = (UIPanel) context.getApplication().createComponent("javax.faces.Panel"); // PENDING I think this can be skipped because we don't render // this component instance. facetComponent.setRendererType("javax.faces.Group"); // PENDING This could possibly be skipped too. However, I think // this is important because other tag handlers, within // <cc:interface> expect it will be there. tmp.getFacets().put(COMPOSITE_FACET_NAME, facetComponent); // We have to put the resource in here just so the classes that eventually // get called by facelets have access to it. tmp.getAttributes().put(COMPONENT_RESOURCE_KEY, ccResource); Facelet facelet; try { facelet = factory.getFacelet(context, ccResource.getURL()); VariableMapper wrapper = new VariableMapperWrapper(orig) { @Override public ValueExpression resolveVariable(String variable) { return super.resolveVariable(variable); } }; faceletContext.setVariableMapper(wrapper); context.getAttributes().put(IS_BUILDING_METADATA, TRUE); // Because mojarra currently requires a <cc:interface> // element within the compcomp markup, we can rely on the // fact that its tag handler, InterfaceHandler.apply(), is // called. In this method, we first imbue facetComponent // with any config information present on the <cc:interface> // element. // Then we do the normal facelet thing: // this.nextHandler.apply(). This causes any child tag // handlers of the <cc:interface> to be called. The // compcomp spec says each such tag handler is responsible // for adding to the compcomp metadata, referenced from the // facetComponent parent. facelet.apply(context, facetComponent); // When facelet.apply() returns (and therefore // InterfaceHandler.apply() returns), the compcomp metadata // pointed to by facetComponent is fully populated. } catch (Exception e) { if (e instanceof FacesException) { throw (FacesException) e; } else { throw new FacesException(e); } } finally { context.getAttributes().remove(IS_BUILDING_METADATA); faceletContext.setVariableMapper(orig); } // we extract the compcomp metadata and return it, making sure // to discard tmp and facetComponent. The compcomp metadata // should be cacheable and shareable across threads, but this is // not yet implemented. return (CompositeComponentBeanInfo) tmp.getAttributes().get(BEANINFO_KEY); } /** * @see javax.faces.view.ViewDeclarationLanguage#getScriptComponentResource(javax.faces.context.FacesContext, javax.faces.application.Resource) */ @Override public Resource getScriptComponentResource(FacesContext context, Resource componentResource) { notNull("context", context); notNull("componentResource", componentResource); return null; } /** * @see ViewHandlingStrategy#retargetAttachedObjects(javax.faces.context.FacesContext, javax.faces.component.UIComponent, java.util.List) */ @SuppressWarnings({"unchecked"}) @Override public void retargetAttachedObjects(FacesContext context, UIComponent topLevelComponent, List<AttachedObjectHandler> handlers) { notNull("context", context); notNull("topLevelComponent", topLevelComponent); notNull("handlers", handlers); if (handlers == null || handlers.isEmpty()) { return; } BeanInfo componentBeanInfo = (BeanInfo) topLevelComponent.getAttributes().get(BEANINFO_KEY); if (componentBeanInfo == null) { return; } BeanDescriptor componentDescriptor = componentBeanInfo.getBeanDescriptor(); // There is an entry in targetList for each attached object in the // <composite:interface> section of the composite component. List<AttachedObjectTarget> targetList = (List<AttachedObjectTarget>) componentDescriptor.getValue(ATTACHED_OBJECT_TARGETS_KEY); // Each entry in targetList will vend one or more UIComponent instances // that is to serve as the target of an attached object in the consuming // page. List<UIComponent> targetComponents; String forAttributeValue, curTargetName; // For each of the attached object handlers... for (AttachedObjectHandler curHandler : handlers) { // Get the name given to this attached object by the page author // in the consuming page. forAttributeValue = curHandler.getFor(); // For each of the attached objects in the <composite:interface> section // of this composite component... for (AttachedObjectTarget curTarget : targetList) { // Get the name given to this attached object target by the // composite component author curTargetName = curTarget.getName(); targetComponents = curTarget.getTargets(topLevelComponent); if (curHandler instanceof ActionSource2AttachedObjectHandler && curTarget instanceof ActionSource2AttachedObjectTarget) { if (forAttributeValue.equals(curTargetName)) { for (UIComponent curTargetComponent : targetComponents) { retargetHandler(context, curHandler, curTargetComponent); } break; } } else if (curHandler instanceof EditableValueHolderAttachedObjectHandler && curTarget instanceof EditableValueHolderAttachedObjectTarget) { if (forAttributeValue.equals(curTargetName)) { for (UIComponent curTargetComponent : targetComponents) { retargetHandler(context, curHandler, curTargetComponent); } break; } } else if (curHandler instanceof ValueHolderAttachedObjectHandler && curTarget instanceof ValueHolderAttachedObjectTarget) { if (forAttributeValue.equals(curTargetName)) { for (UIComponent curTargetComponent : targetComponents) { retargetHandler(context, curHandler, curTargetComponent); } break; } } else if(curHandler instanceof BehaviorHolderAttachedObjectHandler && curTarget instanceof BehaviorHolderAttachedObjectTarget) { BehaviorHolderAttachedObjectHandler behaviorHandler = (BehaviorHolderAttachedObjectHandler) curHandler; BehaviorHolderAttachedObjectTarget behaviorTarget = (BehaviorHolderAttachedObjectTarget) curTarget; String eventName = behaviorHandler.getEventName(); if((null !=eventName && eventName.equals(curTargetName))||(null ==eventName && behaviorTarget.isDefaultEvent())){ for (UIComponent curTargetComponent : targetComponents) { retargetHandler(context, curHandler, curTargetComponent); } } } } } } /** * @see ViewHandlingStrategy#retargetMethodExpressions(javax.faces.context.FacesContext, javax.faces.component.UIComponent) */ @Override public void retargetMethodExpressions(FacesContext context, UIComponent topLevelComponent) { notNull("context", context); notNull("topLevelComponent", topLevelComponent); BeanInfo componentBeanInfo = (BeanInfo) topLevelComponent.getAttributes().get(BEANINFO_KEY); // PENDING(edburns): log error message if componentBeanInfo is null; if (componentBeanInfo == null) { return; } PropertyDescriptor attributes[] = componentBeanInfo.getPropertyDescriptors(); MethodMetadataIterator allMetadata = new MethodMetadataIterator(context, attributes); for (CompCompInterfaceMethodMetadata metadata : allMetadata) { String attrName = metadata.getName(); String[] targets = metadata.getTargets(context); Object attrValue = topLevelComponent.getValueExpression(attrName); // In all cases but one, the attrValue will be a ValueExpression. // The only case when it will not be a ValueExpression is // the case when the attrName is an action, and even then, it'll be a // ValueExpression in all cases except when it's a literal string. if (attrValue == null) { Map<String, Object> attrs = topLevelComponent.getAttributes(); attrValue = attrs.containsKey(attrName) ? attrs.get(attrName) : metadata.getDefault(); if (attrValue == null) { if (metadata.isRequired(context)) { Object location = attrs.get(VIEW_LOCATION_KEY); if (location == null) { location = ""; } throw new FacesException( location.toString() + ": Unable to find attribute with name \"" + attrName + "\" in top level component in consuming page, " + " or with default value in composite component. " + "Page author or composite component author error."); } else { continue; } } } String targetAttributeName = metadata.getTargetAttributeName(context); UIComponent targetComp = null; if (targetAttributeName != null) { attrName = targetAttributeName; } if (targets != null) { MethodRetargetHandler handler = retargetHandlerManager.getRetargetHandler(attrName); if (handler != null) { for (String curTarget : targets) { targetComp = topLevelComponent.findComponent(curTarget); if (targetComp == null) { throw new FacesException( attrValue.toString() + " : Unable to re-target MethodExpression as inner component referenced by target id '" + curTarget + "' cannot be found." ); } handler.retarget(context, metadata, attrValue, targetComp); } } else { // the developer has specified a target for a MethodExpression // but the attribute name doesn't match one action, actionListener, // validator, or valueChangeListener. We can ignore the // target(s) in this case if (LOGGER.isLoggable(WARNING)) { LOGGER.log(WARNING, "jsf.compcomp.unecessary.targets.attribute", new Object[] { getCompositeComponentName(topLevelComponent), attrName }); } handler = retargetHandlerManager.getDefaultHandler(); handler.retarget(context, metadata, attrValue, topLevelComponent); } } else { MethodRetargetHandler handler = null; if (targetAttributeName != null) { targetComp = topLevelComponent.findComponent(metadata.getName()); handler = retargetHandlerManager.getRetargetHandler(attrName); } if (handler == null) { targetComp = topLevelComponent; handler = retargetHandlerManager.getDefaultHandler(); } handler.retarget(context, metadata, attrValue, targetComp); } // clear out the ValueExpression that we've retargeted as a // MethodExpression topLevelComponent.setValueExpression(attrName, null); } } @Override public UIComponent createComponent(FacesContext context, String taglibURI, String tagName, Map<String, Object> attributes) { notNull("context", context); notNull("taglibURI", taglibURI); notNull("tagName", tagName); return associate.getFaceletFactory()._createComponent(context, taglibURI, tagName, attributes); } @Override public List<String> calculateResourceLibraryContracts(FacesContext context, String viewId) { List<String> result = null; String longestPattern = null; if (contractMappings == null) { return emptyList(); } String longestMatch = null; for (Map.Entry<String, List<String>> mappings : contractMappings.entrySet()) { String urlPattern = mappings.getKey(); if (urlPattern.endsWith("*")) { String prefix = urlPattern.substring(0, urlPattern.length() - 1); if (viewId.startsWith(prefix)) { if (longestPattern == null) { longestPattern = urlPattern; longestMatch = prefix; } else if (longestMatch.length() < prefix.length()) { longestPattern = urlPattern; longestMatch = prefix; } } } else if (viewId.equals(urlPattern)) { longestPattern = urlPattern; break; } } if (longestPattern != null) { result = contractMappings.get(longestPattern); } if (result == null) { result = contractMappings.get("*"); } return result; } @Override @SuppressWarnings("deprecation") public boolean viewExists(FacesContext context, String viewId) { if (handlesViewId(viewId)) { return getFaceletFactory().getResourceResolver().resolveUrl(viewId) != null; } return false; } /** * @see javax.faces.view.ViewDeclarationLanguage#getViews(FacesContext, String) */ @Override public Stream<String> getViews(FacesContext context, String path, ViewVisitOption... options) { return mapIfNeeded( super.getViews(context, path) .filter(viewId -> handlesViewId(viewId)), options ); } /** * @see javax.faces.view.ViewDeclarationLanguage#getViews(FacesContext, String, int) */ @Override public Stream<String> getViews(FacesContext context, String path, int maxDepth, ViewVisitOption... options) { return mapIfNeeded( super.getViews(context, path, maxDepth) .filter(viewId -> handlesViewId(viewId)), options ); } // --------------------------------------- Methods from ViewHandlingStrategy /** * @param viewId the view ID to check * @return <code>true</code> if assuming a default configuration and the * view ID's extension is <code>.xhtml</code> Otherwise try to match * the view ID based on the configured extendsion and prefixes. * * @see com.sun.faces.config.WebConfiguration.WebContextInitParameter#FaceletsViewMappings */ @Override public boolean handlesViewId(String viewId) { if (viewId != null) { if (handlesByPrefixOrSuffix(viewId)) { return true; } if (isViewIdExactMappedToFacesServlet(viewId)) { // If the Facelets VDL is reached, no other ViewDeclarationLanguage has declared // to handle the view (via ViewExists()), so we handle it if the viewId happens to be exact // mapped to the FacesServlet. The JSP ViewDeclarationLanguage still comes after us, // but we don't support that. return true; } } return false; } private boolean handlesByPrefixOrSuffix(String viewId) { if (viewId.endsWith(FLOW_DEFINITION_ID_SUFFIX)) { return true; } // If there's no extensions array or prefixes array, then assume defaults. // .xhtml extension is handled by the FaceletViewHandler and .jsp will be handled by // the JSP view handler if (extensionsArray == null && prefixesArray == null) { return isMatchedWithFaceletsSuffix(viewId) ? true : viewId.endsWith(DEFAULT_FACELETS_SUFFIX); } if (extensionsArray != null) { for (String extension : extensionsArray) { if (viewId.endsWith(extension)) { return true; } } } if (prefixesArray != null) { for (String prefix : prefixesArray) { if (viewId.startsWith(prefix)) { return true; } } } return false; } @Override public String getId() { return FACELETS_VIEW_DECLARATION_LANGUAGE_ID; } // ------------------------------------------------------- Protected Methods /** * Initialize the core Facelets runtime. */ protected void initialize() { if (LOGGER.isLoggable(FINE)) { LOGGER.fine("Initializing FaceletViewHandlingStrategy"); } this.initializeMappings(); metadataCache = new Cache<>(new Factory<Resource, BeanInfo>() { @Override public BeanInfo newInstance(Resource ccResource) throws InterruptedException { FacesContext context = FacesContext.getCurrentInstance(); return FaceletViewHandlingStrategy.this.createComponentMetadata(context, ccResource); } }); try { responseBufferSizeSet = webConfig.isSet(FaceletsBufferSize); responseBufferSize = Integer.parseInt(webConfig.getOptionValue(FaceletsBufferSize)); } catch (NumberFormatException nfe) { responseBufferSize = Integer.parseInt(FaceletsBufferSize.getDefaultValue()); } if (LOGGER.isLoggable(FINE)) { LOGGER.fine("Initialization Successful"); } vdlFactory = (ViewDeclarationLanguageFactory) FactoryFinder.getFactory(VIEW_DECLARATION_LANGUAGE_FACTORY); FacesContext context = FacesContext.getCurrentInstance(); ExternalContext extContext = context.getExternalContext(); Map<String, Object> appMap = extContext.getApplicationMap(); @SuppressWarnings("unchecked") Map<String, List<String>> contractDataStructure = (Map<String, List<String>>) appMap.remove(RESOURCE_LIBRARY_CONTRACT_DATA_STRUCTURE_KEY); if (!isEmpty(contractDataStructure)) { contractMappings = new ConcurrentHashMap<>(); for (Map.Entry<String, List<String>> cur : contractDataStructure.entrySet()) { contractMappings.put(cur.getKey(), new CopyOnWriteArrayList<>(cur.getValue())); cur.getValue().clear(); } contractDataStructure.clear(); } if (context != null) { StateManager stateManager = Util.getStateManager(context); if (stateManager != null) { isTrinidadStateManager = stateManager.getClass().getName().contains("trinidad"); } } } /** * Initialize mappings, during the first request. */ protected void initializeMappings() { String viewMappings = webConfig.getOptionValue(FaceletsViewMappings); if (viewMappings != null && viewMappings.length() > 0) { Map<String, Object> appMap = FacesContext.getCurrentInstance().getExternalContext().getApplicationMap(); String[] mappingsArray = split(appMap, viewMappings, ";"); List<String> extensionsList = new ArrayList<>(mappingsArray.length); List<String> prefixesList = new ArrayList<>(mappingsArray.length); for (String aMappingsArray : mappingsArray) { String mapping = aMappingsArray.trim(); int mappingLength = mapping.length(); if (mappingLength <= 1) { continue; } if (mapping.charAt(0) == '*') { extensionsList.add(mapping.substring(1)); } else if (mapping.charAt(mappingLength - 1) == '*') { prefixesList.add(mapping.substring(0, mappingLength - 1)); } } extensionsArray = new String[extensionsList.size()]; extensionsList.toArray(extensionsArray); prefixesArray = new String[prefixesList.size()]; prefixesList.toArray(prefixesArray); } } /** * @param context the {@link FacesContext} for the current request * @return a {@link ResponseWriter} for processing the request * @throws IOException if the writer cannot be created */ protected ResponseWriter createResponseWriter(FacesContext context) throws IOException { ExternalContext extContext = context.getExternalContext(); RenderKit renderKit = context.getRenderKit(); // Avoid a cryptic NullPointerException when the renderkit ID // is incorrectly set if (renderKit == null) { String id = context.getViewRoot().getRenderKitId(); throw new IllegalStateException( "No render kit was available for id \"" + id + "\""); } if (responseBufferSizeSet) { // set the buffer for content extContext.setResponseBufferSize(responseBufferSize); } // get our content type String contentType = (String) context.getAttributes().get("facelets.ContentType"); // get the encoding 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 // is really going to ask for ResponseWriter writer = renderKit.createResponseWriter(NullWriter.INSTANCE, contentType, encoding); contentType = getResponseContentType(context, writer.getContentType()); encoding = getResponseEncoding(context, writer.getCharacterEncoding()); // apply them to the response char[] buffer = new char[1028]; HtmlUtils.writeTextForXML(writer, contentType, buffer); String str = String.valueOf(buffer).trim(); extContext.setResponseContentType(str); extContext.setResponseCharacterEncoding(encoding); // Now, clone with the real writer writer = writer.cloneWithWriter(extContext.getResponseOutputWriter()); return writer; } /** * Handles the case where rendering throws an Exception. * * @param context the {@link FacesContext} for the current request * @param e the caught Exception * @throws IOException if the custom debug content cannot be written */ protected void handleRenderException(FacesContext context, Exception e) throws IOException { // Always log if (LOGGER.isLoggable(SEVERE)) { UIViewRoot root = context.getViewRoot(); StringBuffer sb = new StringBuffer(64); sb.append("Error Rendering View"); if (root != null) { sb.append('['); sb.append(root.getViewId()); sb.append(']'); } LOGGER.log(SEVERE, sb.toString(), e); } if (e instanceof RuntimeException) { throw (RuntimeException) e; } else if (e instanceof IOException) { throw (IOException) e; } else { throw new FacesException(e.getMessage(), e); } } /** * Handles the case where a Facelet cannot be found. * * @param context the {@link FacesContext} for the current request * @param viewId the view ID that was to be mapped to a Facelet * @param message optional message to include in the 404 * @throws IOException if an error occurs sending the 404 to the client */ protected void handleFaceletNotFound(FacesContext context, String viewId, String message) throws IOException { context.getExternalContext().responseSendError( SC_NOT_FOUND, message != null ? viewId + ": " + message : viewId ); 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 (null != context.getExternalContext().getSession(false)) { Map<String, Object> 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<Object,Object> 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<String, Object> 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 * @return the content type to be used for this response */ protected String getResponseContentType(FacesContext context, String orig) { String contentType = orig; // See if we need to override the contentType Map<Object,Object> m = context.getAttributes(); if (m.containsKey("facelets.ContentType")) { contentType = (String) m.get("facelets.ContentType"); if (LOGGER.isLoggable(FINEST)) { LOGGER.finest("Facelet specified alternate contentType '" + contentType + "'"); } } // Safety check if (contentType == null) { contentType = "text/html"; if (LOGGER.isLoggable(FINEST)) { LOGGER.finest("ResponseWriter created had a null ContentType, defaulting to text/html"); } } return contentType; } // --------------------------------------------------------- Private Methods private String getCompositeComponentName(UIComponent compositeComponent) { Resource resource = (Resource) compositeComponent.getAttributes().get(Resource.COMPONENT_RESOURCE_KEY); String name = resource.getResourceName(); String library = resource.getLibraryName(); if (library != null) { return "Composite Component: " + name + ", library: " + library; } else { return "Composite Component: " + name; } } private void doPostBuildActions(FacesContext ctx, UIViewRoot root) { StateContext stateCtx = StateContext.getStateContext(ctx); // if (stateCtx.isPartialStateSaving(ctx, root.getViewId())) { // lu4242 root.markInitialState(); // } stateCtx.startTrackViewModifications(ctx, root); } private void markInitialState(FacesContext ctx, UIViewRoot root) { StateContext stateCtx = StateContext.getStateContext(ctx); if (stateCtx.isPartialStateSaving(ctx, root.getViewId())) { try { ctx.getAttributes().put(IS_BUILDING_INITIAL_STATE, Boolean.TRUE); if (!root.isTransient()) { markInitialState(root); } } finally { ctx.getAttributes().remove(IS_BUILDING_INITIAL_STATE); } } } private void markInitialState(final UIComponent component) { component.markInitialState(); for (Iterator<UIComponent> it = component.getFacetsAndChildren() ; it.hasNext() ; ) { UIComponent child = it.next(); if (!child.isTransient()) { markInitialState(child); } } } private void retargetHandler(FacesContext context, AttachedObjectHandler handler, UIComponent targetComponent) { if (UIComponent.isCompositeComponent(targetComponent)) { // RELEASE_PENDING Not keen on calling CompositeComponentTagHandler here.... List<AttachedObjectHandler> nHandlers = CompositeComponentTagHandler .getAttachedObjectHandlers(targetComponent); nHandlers.add(handler); retargetAttachedObjects(context, targetComponent, nHandlers); } else { handler.applyAttachedObject(context, targetComponent); } } // ---------------------------------------------------------- Nested Classes /** * Provides iteration services over a composite component's * MethodExpression-enabled <code>PropertyDescriptors</code>. */ private static final class MethodMetadataIterator implements Iterable<CompCompInterfaceMethodMetadata>, Iterator<CompCompInterfaceMethodMetadata> { private final PropertyDescriptor[] descriptors; private FacesContext context; private int curIndex = -1; // -------------------------------------------------------- Constructors MethodMetadataIterator(FacesContext context, PropertyDescriptor[] descriptors) { this.context = context; this.descriptors = descriptors; if (descriptors != null && descriptors.length > 0) { curIndex = 0; } } // ----------------------------------------------- Methods from Iterable @Override public Iterator<CompCompInterfaceMethodMetadata> iterator() { return this; } // ----------------------------------------------- Methods from Iterator @Override public boolean hasNext() { if (curIndex != -1 && curIndex < descriptors.length) { int idx = curIndex; while (idx < descriptors.length) { PropertyDescriptor pd = descriptors[idx]; if (shouldSkip(pd)) { // this is a ValueExpression-enabled attribute and // should be ignored. idx++; } else { if (idx != curIndex) { // the PD that was found to be returned by the // next() call has a different offset from the // current index; update the current index. curIndex = idx; } return (curIndex < descriptors.length); } } } return false; } @Override public CompCompInterfaceMethodMetadata next() { return new CompCompInterfaceMethodMetadata(descriptors[curIndex++]); } @Override public void remove() { throw new UnsupportedOperationException(); } private boolean shouldSkip(PropertyDescriptor pd) { boolean result; String name = pd.getName(); ValueExpression ve = (ValueExpression) pd.getValue("targetAttributeName"); String targetAttributeName = ((ve != null) ? (String) ve.getValue(context.getELContext()) : ""); boolean isSpecialAttributeName = Util.isSpecialAttributeName(name) || Util.isSpecialAttributeName(targetAttributeName); result = (!isSpecialAttributeName && (pd.getValue("type") != null || pd.getValue("method-signature") == null)); return result; } } // END MethodMetadataIterator /** * Utility class to encapsulate the ValueExpression evaluation of the various * MethodExpression composite component properties. */ private static final class CompCompInterfaceMethodMetadata { private final PropertyDescriptor pd; // -------------------------------------------------------- Constructors CompCompInterfaceMethodMetadata(PropertyDescriptor pd) { this.pd = pd; } // ------------------------------------------------------ Public Methods /** * @param ctx the <code>FacesContext</code> for the current request * @return the <code>method-signature</code> for this attribute */ public String getMethodSignature(FacesContext ctx) { ValueExpression ms = (ValueExpression) pd.getValue("method-signature"); if (ms != null) { return (String) ms.getValue(ctx.getELContext()); } return null; } /** * @param ctx the <code>FacesContext</code> for the current request * @return an array of component targets to which a MethodExpression * should be retargeted */ public String[] getTargets(FacesContext ctx) { ValueExpression ts = (ValueExpression) pd.getValue("targets"); if (ts != null) { String targets = (String) ts.getValue(ctx.getELContext()); if (targets != null) { return Util.split(ctx.getExternalContext().getApplicationMap(), targets, " "); } } return null; } public String getTargetAttributeName(FacesContext ctx) { ValueExpression ve = (ValueExpression) pd.getValue("targetAttributeName"); return ((ve != null) ? (String) ve.getValue(ctx.getELContext()) : null); } /** * @param ctx the <code>FacesContext</code> for the current request * @return <code>true<code> if this attribute is required to be present, * otherwise, returns <code>false</code> */ public boolean isRequired(FacesContext ctx) { ValueExpression rd = (ValueExpression) pd.getValue("required"); return ((rd != null) ? Boolean.valueOf(rd.getValue(ctx.getELContext()).toString()) : false); } /** * @return the default value as designated by the composite component * author if no attribute was specified by the composite component * consumer. This value may be a ValueExpression, or a literal. */ public Object getDefault() { return pd.getValue("default"); } /** * @return the composite component attribute name */ public String getName() { return pd.getName(); } } // END CompCompInterfaceMethodMetadata /** * Managed the <code>MethodRetargetHandler</code> implementations for the * current <code>MethodExpression</code> enabled component attributes: * <ul> * <li>action</li> * <li>actionListener</li> * <li>validator</li> * <li>valueChangeListener</li> * </ul> * * Instances of this object also provide a default handler that can be * used to re-target <code>MethodExperssions</code> that don't match * on of the four names described above. */ private static final class MethodRetargetHandlerManager { private Map<String,MethodRetargetHandler> handlerMap = new HashMap<>(4, 1.0f); private MethodRetargetHandler arbitraryHandler = new ArbitraryMethodRegargetHandler(); // -------------------------------------------------------- Constructors MethodRetargetHandlerManager() { MethodRetargetHandler[] handlers = { new ActionRegargetHandler(), new ActionListenerRegargetHandler(), new ValidatorRegargetHandler(), new ValueChangeListenerRegargetHandler() }; for (MethodRetargetHandler h : handlers) { handlerMap.put(h.getAttribute(), h); } } // ------------------------------------------------------ Public Methods /** * Lookup/return a <code>MethodRetargetHandler</code> appropriate to the * provided attribute name * @param attrName the attribute name * @return a <code>MethodRetargetHandler</code> that can properly handle * retargeting expressions for the specified attribute, or </code>null</code> * if there is no handler available. */ private MethodRetargetHandler getRetargetHandler(String attrName) { return handlerMap.get(attrName); } /** * @return a <code>MethodRetargetHandler</code> that can retarget * arbitrarily named MethodExpressions. */ private MethodRetargetHandler getDefaultHandler() { return arbitraryHandler; } // ------------------------------------------------------ Nested Classes /** * Base MethodRetargetHandler implementation. */ private static abstract class AbstractRetargetHandler implements MethodRetargetHandler { protected static final Class[] NO_ARGS = new Class[0]; } // END AbstractRetargetHandler /** * This handler is responsible for creating/retargeting MethodExpressions defined * associated with the <code>action</code> attribute */ private static final class ActionRegargetHandler extends AbstractRetargetHandler { private static final String ACTION = "action"; // ------------------------------ Methods from MethodRetargetHandler @Override public void retarget(FacesContext ctx, CompCompInterfaceMethodMetadata metadata, Object sourceValue, UIComponent target) { String expr = (sourceValue instanceof ValueExpression) ? ((ValueExpression) sourceValue).getExpressionString() : sourceValue.toString(); ExpressionFactory f = ctx.getApplication().getExpressionFactory(); MethodExpression me = f.createMethodExpression(ctx.getELContext(), expr, Object.class, NO_ARGS); ((ActionSource2) target) .setActionExpression( new ContextualCompositeMethodExpression(((sourceValue instanceof ValueExpression) ? (ValueExpression) sourceValue : null), me)); } @Override public String getAttribute() { return ACTION; } } // END ActionRegargetHandler /** * This handler is responsible for creating/retargeting MethodExpressions defined * associated with the <code>actionListener</code> attribute */ private static final class ActionListenerRegargetHandler extends AbstractRetargetHandler { private static final String ACTION_LISTENER = "actionListener"; private static final Class[] ACTION_LISTENER_ARGS = new Class[] { ActionEvent.class }; // ------------------------------ Methods from MethodRetargetHandler @Override public void retarget(FacesContext ctx, CompCompInterfaceMethodMetadata metadata, Object sourceValue, UIComponent target) { ValueExpression ve = (ValueExpression) sourceValue; ExpressionFactory f = ctx.getApplication().getExpressionFactory(); MethodExpression me = f.createMethodExpression(ctx.getELContext(), ve.getExpressionString(), Void.TYPE, ACTION_LISTENER_ARGS); MethodExpression noArg = f.createMethodExpression(ctx.getELContext(), ve.getExpressionString(), Void.TYPE, NO_ARGS); ((ActionSource2) target).addActionListener( new MethodExpressionActionListener( new ContextualCompositeMethodExpression(ve, me), new ContextualCompositeMethodExpression(ve, noArg))); } @Override public String getAttribute() { return ACTION_LISTENER; } } // END ActionListenerRegargetHandler /** * This handler is responsible for creating/retargeting MethodExpressions defined * associated with the <code>validator</code> attribute */ private static final class ValidatorRegargetHandler extends AbstractRetargetHandler { private static final String VALIDATOR = "validator"; private static final Class[] VALIDATOR_ARGS = new Class[]{ FacesContext.class, UIComponent.class, Object.class }; // ------------------------------ Methods from MethodRetargetHandler @Override public void retarget(FacesContext ctx, CompCompInterfaceMethodMetadata metadata, Object sourceValue, UIComponent target) { ValueExpression ve = (ValueExpression) sourceValue; ExpressionFactory f = ctx.getApplication().getExpressionFactory(); MethodExpression me = f.createMethodExpression(ctx.getELContext(), ve.getExpressionString(), Void.TYPE, VALIDATOR_ARGS); ((EditableValueHolder) target).addValidator( new MethodExpressionValidator( new ContextualCompositeMethodExpression(ve, me))); } @Override public String getAttribute() { return VALIDATOR; } } // END ValidatorRegargetHandler /** * This handler is responsible for creating/retargeting MethodExpressions defined * associated with the <code>valueChangeListener</code> attribute */ private static final class ValueChangeListenerRegargetHandler extends AbstractRetargetHandler { private static final String VALUE_CHANGE_LISTENER = "valueChangeListener"; private static final Class[] VALUE_CHANGE_LISTENER_ARGS = new Class[]{ ValueChangeEvent.class }; // ------------------------------ Methods from MethodRetargetHandler @Override public void retarget(FacesContext ctx, CompCompInterfaceMethodMetadata metadata, Object sourceValue, UIComponent target) { ValueExpression ve = (ValueExpression) sourceValue; ExpressionFactory f = ctx.getApplication().getExpressionFactory(); MethodExpression me = f.createMethodExpression(ctx.getELContext(), ve.getExpressionString(), Void.TYPE, VALUE_CHANGE_LISTENER_ARGS); MethodExpression noArg = f.createMethodExpression(ctx.getELContext(), ve.getExpressionString(), Void.TYPE, NO_ARGS); ((EditableValueHolder) target).addValueChangeListener( new MethodExpressionValueChangeListener( new ContextualCompositeMethodExpression(ve, me), new ContextualCompositeMethodExpression(ve, noArg))); } @Override public String getAttribute() { return VALUE_CHANGE_LISTENER; } } // END ValueChangeListenerRegargetHandler /** * This handler is responsible for creating/retargeting MethodExpressions defined * using arbitrary attribute names. */ private static final class ArbitraryMethodRegargetHandler extends AbstractRetargetHandler { // ------------------------------ Methods from MethodRetargetHandler @Override public void retarget(FacesContext ctx, CompCompInterfaceMethodMetadata metadata, Object sourceValue, UIComponent target) { ValueExpression ve = (ValueExpression) sourceValue; ExpressionFactory f = ctx.getApplication() .getExpressionFactory(); // There is no explicit methodExpression property on // an inner component to which this MethodExpression // should be retargeted. In this case, replace the // ValueExpression with a method expresson. // Pull apart the methodSignature to derive the // expectedReturnType and expectedParameters String methodSignature = metadata.getMethodSignature(ctx); assert (null != methodSignature); methodSignature = methodSignature.trim(); Class<?> expectedReturnType; Class<?>[] expectedParameters = NO_ARGS; // Get expectedReturnType int j, i = methodSignature.indexOf(" "); if (-1 != i) { String strValue = methodSignature.substring(0, i); try { expectedReturnType = Util.getTypeFromString(strValue.trim()); } catch (ClassNotFoundException cnfe) { throw new FacesException(methodSignature + " : Unable to load type '" + strValue + '\''); } } else { if (LOGGER.isLoggable(Level.SEVERE)) { LOGGER.severe( "Unable to determine expected return type for " + methodSignature); } return; } // derive the arguments i = methodSignature.indexOf("("); if (-1 != i) { j = methodSignature.indexOf(")", i + 1); if (-1 != j) { String strValue = methodSignature.substring(i + 1, j); if (0 < strValue.length()) { String[] params = strValue.split(","); expectedParameters = new Class[params.length]; boolean exceptionThrown = false; for (i = 0; i < params.length; i++) { try { expectedParameters[i] = Util.getTypeFromString(params[i].trim()); } catch (ClassNotFoundException cnfe) { if (LOGGER.isLoggable(Level.SEVERE)) { LOGGER.log(Level.SEVERE, "Unable to determine parameter type for " + methodSignature, cnfe); } exceptionThrown = true; break; } } if (exceptionThrown) { return; } } else { expectedParameters = NO_ARGS; } } } assert (null != expectedReturnType); assert (null != expectedParameters); // JAVASERVERFACES-4073 ELContext elContext = (ELContext) ctx.getAttributes().get(FaceletContext.FACELET_CONTEXT_KEY); if (null == elContext) { elContext = ctx.getELContext(); } MethodExpression me = f .createMethodExpression(elContext , ve.getExpressionString(), expectedReturnType, expectedParameters); target.getAttributes().put(metadata.getName(), new ContextualCompositeMethodExpression( ve, me)); } @Override public String getAttribute() { return null; } } // END ArbitraryMethodRegargetHandler } // END MethodRegargetHandlerManager /** * Implementations of this interface provide the <code>strategy</code> to * properly retarget a method expression for a particular attribute. */ private interface MethodRetargetHandler { /** * Constructs and retargets a <code>MethodExpression</code> as appropriate * based on the provided arguments. * * @param ctx the <code>FacesContext</code> for the current request * @param metadata the metadata describing the method to be retargeted * @param sourceValue typically, this will be a ValueExpression, however, * there are cases where this could be provided as a literal. It basically * represents the attribute value being passed to the composite component * @param target the component that will be target of the method expression */ void retarget(FacesContext ctx, CompCompInterfaceMethodMetadata metadata, Object sourceValue, UIComponent target); /** * @return the attribute name this <code>MethodRetargetHandler</code> * is designed to handle */ String getAttribute(); } // END MethodRetargetHandler /** * Simple no-op writer. */ protected static final class NullWriter extends Writer { static final NullWriter INSTANCE = new NullWriter(); @Override public void write(char[] buffer) { } @Override public void write(char[] buffer, int off, int len) { } @Override public void write(String str) { } @Override public void write(int c) { } @Override public void write(String str, int off, int len) { } @Override public void close() { } @Override public void flush() { } } /** * Find the given component in the component tree. * * @param context the Faces context. * @param clientId the client id of the component to find. */ private UIComponent locateComponentByClientId(final FacesContext context, final UIComponent parent, final String clientId) { final List<UIComponent> found = new ArrayList<>(); UIComponent result = null; parent.invokeOnComponent(context, clientId, new ContextCallback() { @Override public void invokeContextCallback(FacesContext context, UIComponent target) { found.add(target); } }); /* * Since we did not find it the cheaper way we need to assume there is a * UINamingContainer that does not prepend its ID. So we are going to * walk the tree to find it. */ if (found.isEmpty()) { VisitContext visitContext = VisitContext.createVisitContext(context); parent.visitTree(visitContext, new VisitCallback() { @Override public VisitResult visit(VisitContext visitContext, UIComponent component) { VisitResult result = VisitResult.ACCEPT; if (component.getClientId(visitContext.getFacesContext()).equals(clientId)) { found.add(component); result = VisitResult.COMPLETE; } return result; } }); } if (!found.isEmpty()) { result = found.get(0); } return result; } /** * Reapply the dynamic actions after Facelets reapply. * * <p> Note a precondition to this method is that tracking view * modifications is turned off during the execution of this method. The * caller of this method is responsible for turning tracking view * modifications off and on as required. </p> * * @param context the Faces context. */ private void reapplyDynamicActions(FacesContext context) { StateContext stateContext = StateContext.getStateContext(context); List<ComponentStruct> actions = stateContext.getDynamicActions(); if (actions != null) { for (ComponentStruct action : actions) { if (REMOVE.equals(action.getAction())) { reapplyDynamicRemove(context, action); } if (ADD.equals(action.getAction())) { reapplyDynamicAdd(context, action); } } } } /** * Reapply the dynamic add after Facelets reapply. * * @param context the Faces context. * @param struct the component struct. */ private void reapplyDynamicAdd(FacesContext context, ComponentStruct struct) { UIComponent parent = locateComponentByClientId(context, context.getViewRoot(), struct.getParentClientId()); if (parent != null) { UIComponent child = locateComponentByClientId(context, parent, struct.getClientId()); StateContext stateContext = StateContext.getStateContext(context); if (child == null) { child = stateContext.getDynamicComponents().get(struct.getClientId()); } if (child != null) { if (struct.getFacetName() != null) { parent.getFacets().remove(struct.getFacetName()); parent.getFacets().put(struct.getFacetName(), child); child.getClientId(); } else { int childIndex = -1; if (child.getAttributes().containsKey(DYNAMIC_COMPONENT)) { childIndex = (Integer) child.getAttributes().get(DYNAMIC_COMPONENT); } child.setId(struct.getId()); if (childIndex >= parent.getChildCount() || childIndex == -1) { parent.getChildren().add(child); } else { parent.getChildren().add(childIndex, child); } child.getClientId(); child.getAttributes().put(DYNAMIC_COMPONENT, child.getParent().getChildren().indexOf(child)); } stateContext.getDynamicComponents().put(struct.getClientId(), child); } } } /** * Reapply the dynamic remove after Facelets reapply. * * @param context the Faces context. * @param struct the component struct. */ private void reapplyDynamicRemove(FacesContext context, ComponentStruct struct) { UIComponent child = locateComponentByClientId(context, context.getViewRoot(), struct.getClientId()); if (child != null) { StateContext stateContext = StateContext.getStateContext(context); stateContext.getDynamicComponents().put(struct.getClientId(), child); UIComponent parent = child.getParent(); parent.getChildren().remove(child); } } /** * Are we saving state server side? * * @return true if we are, false otherwise. */ private boolean isServerStateSaving() { if (STATE_SAVING_METHOD_SERVER.equals(webConfig.getOptionValue(StateSavingMethod))) { return true; } return false; } /** * Get a session (if we are using server state saving). * * @param context the Faces context. * @return the session, or null if we are not using server state saving. */ private HttpSession getSession(FacesContext context) { Object sessionObj = context.getExternalContext().getSession(true); if (sessionObj instanceof HttpSession) { return (HttpSession) sessionObj; } return null; } /** * Gets and if needed initializes the faceletFactory * * @return the default faceletFactorys */ private DefaultFaceletFactory getFaceletFactory() { if (faceletFactory == null) { faceletFactory = associate.getFaceletFactory(); assert (faceletFactory != null); } return faceletFactory; } private boolean isMatchedWithFaceletsSuffix(String viewId) { String[] defaultsuffixes = webConfig.getOptionValue(WebConfiguration.WebContextInitParameter.FaceletsSuffix, " "); for (String suffix : defaultsuffixes) { if (viewId.endsWith(suffix)) { return true; } } return false; } private String getMatchedWithFaceletsSuffix(String viewId) { String[] defaultsuffixes = webConfig.getOptionValue(WebConfiguration.WebContextInitParameter.FaceletsSuffix, " "); for (String suffix : defaultsuffixes) { if (viewId.endsWith(suffix)) { return suffix; } } return null; } /** * Maps the element in the passed in stream of views according to the given options. * * @param views The stream of views to potentially map * @param options Options telling if and if so how to map * @return The stream with views, possibly mapped if needed */ private Stream<String> mapIfNeeded(Stream<String> views, ViewVisitOption... options) { if (!returnAsImplicitOutCome(options)) { return views; } return views.map(view -> toImplicitOutcome(view)); } private boolean returnAsImplicitOutCome(ViewVisitOption... options) { return stream(options) .filter(option -> option == RETURN_AS_MINIMAL_IMPLICIT_OUTCOME) .findAny() .isPresent(); } private String toImplicitOutcome(String viewId) { String suffix = getConfiguredSuffix(viewId); if (suffix != null) { return viewId.substring(0, viewId.lastIndexOf(suffix)); } String prefix = getConfiguredPrefix(viewId); if (prefix != null) { return viewId.substring(prefix.length()); } // Would be rare to reach this, since when toImplicitOutcome() is called // handlesViewId() should already have been called before that // and then either suffix or prefix would have matched. return viewId; } private String getConfiguredSuffix(String viewId) { if (viewId != null) { if (viewId.endsWith(FLOW_DEFINITION_ID_SUFFIX)) { return FLOW_DEFINITION_ID_SUFFIX; } // If there's no extensions array or prefixes array, then // assume defaults. .xhtml extension is handled by // the FaceletViewHandler and .jsp will be handled by // the JSP view handler if (extensionsArray == null && prefixesArray == null) { String suffix = getMatchedWithFaceletsSuffix(viewId); if (suffix != null) { return suffix; } if (viewId.endsWith(DEFAULT_FACELETS_SUFFIX)) { return DEFAULT_FACELETS_SUFFIX; } } if (extensionsArray != null) { for (String extension : extensionsArray) { if (viewId.endsWith(extension)) { return extension; } } } } return null; } private String getConfiguredPrefix(String viewId) { if (prefixesArray != null) { for (String prefix : prefixesArray) { if (viewId.startsWith(prefix)) { return prefix; } } } return null; } /** * Mark the initial state if not already marked. */ private void markInitialStateIfNotMarked(UIComponent component) { if (!component.isTransient()) { if (!component.getAttributes().containsKey(DYNAMIC_COMPONENT) && !component.initialStateMarked()) { component.markInitialState(); } for (Iterator<UIComponent> it = component.getFacetsAndChildren() ; it.hasNext() ; ) { UIComponent child = it.next(); markInitialStateIfNotMarked(child); } } } }