| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| package org.apache.myfaces.view.facelets; |
| |
| import java.util.ArrayList; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.logging.Level; |
| import java.util.logging.Logger; |
| import javax.faces.application.Application; |
| import javax.faces.application.NavigationHandler; |
| import javax.faces.application.ProjectStage; |
| import javax.faces.application.ResourceDependency; |
| import javax.faces.application.ViewHandler; |
| import javax.faces.component.UIComponent; |
| import javax.faces.component.UIViewRoot; |
| import javax.faces.context.FacesContext; |
| import javax.faces.event.PhaseId; |
| import javax.faces.view.StateManagementStrategy; |
| import javax.faces.view.ViewDeclarationLanguage; |
| import org.apache.myfaces.application.StateManagerImpl; |
| import org.apache.myfaces.component.ComponentResourceContainer; |
| import org.apache.myfaces.context.RequestViewContext; |
| import org.apache.myfaces.context.RequestViewMetadata; |
| import org.apache.myfaces.lifecycle.RestoreViewSupport; |
| import org.apache.myfaces.config.MyfacesConfig; |
| import org.apache.myfaces.util.WebConfigParamUtils; |
| import org.apache.myfaces.view.facelets.impl.FaceletCompositionContextImpl; |
| import org.apache.myfaces.view.facelets.pool.ViewPool; |
| import org.apache.myfaces.view.facelets.pool.ViewPoolFactory; |
| import org.apache.myfaces.view.facelets.pool.ViewEntry; |
| import org.apache.myfaces.view.facelets.pool.ViewStructureMetadata; |
| import org.apache.myfaces.view.facelets.pool.impl.ViewPoolFactoryImpl; |
| import org.apache.myfaces.view.facelets.tag.jsf.ComponentSupport; |
| import org.apache.myfaces.view.facelets.tag.jsf.FaceletState; |
| |
| /** |
| * This class is reponsible for all processing tasks related to the view pool. |
| * |
| * For enable the pool only for a subset of your views, you can |
| * add an entry inside faces-config.xml file like this: |
| * <pre> |
| * {@code |
| * <faces-config-extension> |
| * <view-pool-mapping> |
| * <url-pattern>/*</url-pattern> |
| * <parameter> |
| * <name>org.apache.myfaces.VIEW_POOL_MAX_POOL_SIZE</name> |
| * <value>5</value> |
| * </parameter> |
| * </view-pool-mapping> |
| * </faces-config-extension> |
| * } |
| * </pre> |
| * |
| * @author Leonardo Uribe |
| */ |
| public class ViewPoolProcessor |
| { |
| /** |
| * Used to hold the view pool processor instance on the application map. |
| * A ViewPoolProcessor is only accessible if the view pool has been |
| * enabled for the whole application (using a web config parameter) or |
| * partially (an <view-pool-mapping> entry inside <faces-config-extension>). |
| */ |
| private final static String INSTANCE = "oam.ViewPoolProcessor"; |
| |
| /** |
| * UIViewRoot attribute to enable/disable the view for use pooling. |
| */ |
| public final static String ENABLE_VIEW_POOL = "oamEnableViewPool"; |
| |
| /** |
| * Flag that indicates to the StateManagementStrategy that no state needs |
| * to be stored and we are using saveState() to dispose the view and store it |
| * into the pool directly. |
| */ |
| public final static String FORCE_HARD_RESET = "oam.ViewPool.forceHardReset"; |
| |
| /** |
| * Flag to indicate that dispose this view on navigation is valid. |
| */ |
| public final static String DISPOSE_VIEW_NAVIGATION = "oam.ViewPool.disposeViewOnNavigation"; |
| |
| /** |
| * Attribute of UIViewRoot that indicates if a soft (1) or hard(2) |
| * (reset and check) reset is required in the call to saveState(). |
| */ |
| public final static String RESET_SAVE_STATE_MODE_KEY ="oam.view.resetSaveStateMode"; |
| |
| /** |
| * Indicates no reset should be done on this state saving |
| */ |
| public static final Integer RESET_MODE_OFF = 0; |
| |
| /** |
| * Indicates a soft reset should be done when saveState(...) is performed, |
| * which means all transient state should be cleared but the delta state |
| * should not be destroyed in the process. |
| */ |
| public static final Integer RESET_MODE_SOFT = 1; |
| |
| /** |
| * Indicates a hard reset should be done when saveState(...) is performed, |
| * which means all transient and delta state should be cleared, destroying |
| * all existing state in the process. If something cannot be reseted, the |
| * state should return non null, so the algorithm can remove the component |
| * from the tree and mark the tree as partial (requires refresh before |
| * reuse). |
| */ |
| public static final Integer RESET_MODE_HARD = 2; |
| |
| /** |
| * Param used to indicate a "deferred navigation" needs to be done. To allow the view pool to |
| * dispose the view properly (and reuse it later), it is necessary to ensure the view is not |
| * being used at the moment. If the navigation call occur inside an action listener, the current |
| * view is being used and cannot be disposed (because it conflicts with hard/soft reset). This |
| * extension allows to call handleNavigation() before end invoke application phase, but after |
| * traverse the component tree. |
| */ |
| public static final String INVOKE_DEFERRED_NAVIGATION = "oam.invoke.navigation"; |
| |
| private ViewPoolFactory viewPoolFactory; |
| private RestoreViewSupport restoreViewSupport; |
| |
| public ViewPoolProcessor(FacesContext context) |
| { |
| viewPoolFactory = new ViewPoolFactoryImpl(context); |
| restoreViewSupport = new RestoreViewSupport(context); |
| } |
| |
| public static ViewPoolProcessor getInstance(FacesContext context) |
| { |
| return (ViewPoolProcessor) context.getExternalContext().getApplicationMap().get(INSTANCE); |
| } |
| |
| /** |
| * This method should be called at startup to decide if a view processor should be |
| * provided or not to the runtime. |
| * |
| * @param context |
| */ |
| public static void initialize(FacesContext context) |
| { |
| if (context.isProjectStage(ProjectStage.Production)) |
| { |
| MyfacesConfig myfacesConfig = MyfacesConfig.getCurrentInstance(context); |
| |
| boolean initialize = true; |
| |
| if (myfacesConfig.getELExpressionCacheMode() != ELExpressionCacheMode.alwaysRecompile) |
| { |
| Logger.getLogger(ViewPoolProcessor.class.getName()).log(Level.INFO, |
| MyfacesConfig.CACHE_EL_EXPRESSIONS |
| + " web config parameter is set to \"" |
| + ((myfacesConfig.getELExpressionCacheMode() == null) |
| ? "none" : myfacesConfig.getELExpressionCacheMode()) |
| + "\". To enable view pooling this param" |
| + " must be set to \"alwaysRecompile\". View Pooling disabled."); |
| initialize = false; |
| } |
| |
| long refreshPeriod = WebConfigParamUtils.getLongInitParameter(context.getExternalContext(), |
| ViewHandler.FACELETS_REFRESH_PERIOD_PARAM_NAME, |
| FaceletViewDeclarationLanguage.DEFAULT_REFRESH_PERIOD_PRODUCTION); |
| |
| if (refreshPeriod != -1) |
| { |
| Logger.getLogger(ViewPoolProcessor.class.getName()).log( |
| Level.INFO, ViewHandler.FACELETS_REFRESH_PERIOD_PARAM_NAME + |
| " web config parameter is set to \"" + Long.toString(refreshPeriod) + |
| "\". To enable view pooling this param"+ |
| " must be set to \"-1\". View Pooling disabled."); |
| initialize = false; |
| } |
| |
| if (myfacesConfig.isStrictJsf2FaceletsCompatibility()) |
| { |
| Logger.getLogger(ViewPoolProcessor.class.getName()).log( |
| Level.INFO, MyfacesConfig.STRICT_JSF_2_FACELETS_COMPATIBILITY + |
| " web config parameter is set to \"" + "true" + |
| "\". To enable view pooling this param "+ |
| " must be set to \"false\". View Pooling disabled."); |
| initialize = false; |
| } |
| |
| if (initialize) |
| { |
| ViewPoolProcessor processor = new ViewPoolProcessor(context); |
| context.getExternalContext(). |
| getApplicationMap().put(INSTANCE, processor); |
| } |
| } |
| } |
| |
| public ViewPool getViewPool(FacesContext context, UIViewRoot root) |
| { |
| if (root.isTransient()) |
| { |
| // Stateless views cannot be pooled, because we are reusing |
| // state saving algorithm for that. |
| return null; |
| } |
| |
| Boolean enableViewPool = (Boolean) root.getAttributes().get(ViewPoolProcessor.ENABLE_VIEW_POOL); |
| if (enableViewPool != null && !Boolean.TRUE.equals(enableViewPool)) |
| { |
| // view pool not enabled for this view. |
| return null; |
| } |
| else |
| { |
| return viewPoolFactory.getViewPool(context, root); |
| } |
| } |
| |
| public boolean isViewPoolEnabledForThisView(FacesContext context, UIViewRoot root) |
| { |
| if (root.isTransient()) |
| { |
| // Stateless views cannot be pooled, because we are reusing |
| // state saving algorithm for that. |
| return false; |
| } |
| |
| Boolean enableViewPool = (Boolean) root.getAttributes().get(ViewPoolProcessor.ENABLE_VIEW_POOL); |
| if (enableViewPool != null) |
| { |
| return enableViewPool; |
| } |
| |
| ViewPool viewPool = getViewPool(context, root); |
| if (viewPool != null) |
| { |
| return true; |
| } |
| |
| return false; |
| } |
| |
| public boolean isViewPoolStrategyAllowedForThisView(FacesContext context, UIViewRoot root) |
| { |
| // Check if the viewId is not null. |
| if (root.getViewId() == null) |
| { |
| return false; |
| } |
| if (root.isTransient()) |
| { |
| // Stateless views cannot be pooled, because we are reusing |
| // state saving algorithm for that. |
| return false; |
| } |
| |
| // Check if the view is enabled or not for use pooling |
| if (!isViewPoolEnabledForThisView(context, root)) |
| { |
| return false; |
| } |
| |
| // Check if the vdl is using PSS on this view (has a vdl and that vdl is facelets) |
| ViewDeclarationLanguage vdl = context.getApplication().getViewHandler(). |
| getViewDeclarationLanguage(context, root.getViewId()); |
| if (vdl == null) |
| { |
| return false; |
| } |
| else if (!ViewDeclarationLanguage.FACELETS_VIEW_DECLARATION_LANGUAGE_ID.equals(vdl.getId())) |
| { |
| return false; |
| } |
| |
| if (vdl.getStateManagementStrategy(context, root.getViewId()) == null) |
| { |
| return false; |
| } |
| return true; |
| } |
| |
| public void setViewPoolDisabledOnThisView(FacesContext context, UIViewRoot root, boolean value) |
| { |
| root.getAttributes().put(ViewPoolProcessor.ENABLE_VIEW_POOL, !value); |
| } |
| |
| /** |
| * Takes the newView and restore the state taken as base the provided ViewEntry, |
| * and then move all child components from oldView to newView, to finally obtain |
| * a clean component tree. |
| * |
| * @param context |
| * @param newView |
| * @param entry |
| */ |
| public void cloneAndRestoreView(FacesContext context, UIViewRoot newView, |
| ViewEntry entry, ViewStructureMetadata metadata) |
| { |
| UIViewRoot oldView = entry.getViewRoot(); |
| // retrieveViewRootInitialState(context, oldView) |
| Object viewState = metadata.getViewRootState(); |
| Object newViewState; |
| UIComponent metadataFacet = newView.getFacet(UIViewRoot.METADATA_FACET_NAME); |
| if (viewState == null) |
| { |
| // (Optional, it should be always metadata) |
| oldView.clearInitialState(); |
| viewState = oldView.saveState(context); |
| } |
| Map<String, Object> viewScopeMap = newView.getViewMap(false); |
| if (viewScopeMap != null && !viewScopeMap.isEmpty()) |
| { |
| newViewState = newView.saveState(context); |
| } |
| else |
| { |
| newViewState = null; |
| } |
| |
| boolean oldProcessingEvents = context.isProcessingEvents(); |
| context.setProcessingEvents(false); |
| try |
| { |
| if (oldView.getFacetCount() > 0) |
| { |
| List<String> facetKeys = new ArrayList<>(oldView.getFacets().keySet()); |
| |
| for (String facetKey : facetKeys) |
| { |
| //context.setProcessingEvents(false); |
| if (metadataFacet != null && UIViewRoot.METADATA_FACET_NAME.equals(facetKey) && |
| !PhaseId.RESTORE_VIEW.equals(context.getCurrentPhaseId())) |
| { |
| // Metadata facet is special, it is created when ViewHandler.createView(...) is |
| // called, so it shouldn't be taken from the oldView, otherwise the state |
| // will be lost. Instead reuse it and discard the one in oldView. |
| // But on restore view phase, use the old one, because the new one does not |
| // have initial state marked. |
| newView.getFacets().put(facetKey, metadataFacet); |
| } |
| else |
| { |
| UIComponent facet = oldView.getFacets().remove(facetKey); |
| //context.setProcessingEvents(true); |
| newView.getFacets().put(facetKey, facet); |
| } |
| } |
| } |
| if (oldView.getChildCount() > 0) |
| { |
| for (Iterator<UIComponent> it = oldView.getChildren().iterator(); it.hasNext();) |
| { |
| //context.setProcessingEvents(false); |
| UIComponent c = it.next(); |
| it.remove(); |
| //context.setProcessingEvents(true); |
| newView.getChildren().add(c); |
| } |
| } |
| |
| // Restore the newView as saved just before markInitialState() call |
| newView.restoreState(context, viewState); |
| newView.markInitialState(); |
| |
| if (!PhaseId.RESTORE_VIEW.equals(context.getCurrentPhaseId())) |
| { |
| // Restore bindings like in restore view phase, because in this case, |
| // bindings needs to be set (Application.createComponent is not called!). |
| restoreViewSupport.processComponentBinding(context, newView); |
| |
| // Restore view scope map if necessary |
| if (viewScopeMap != null && !viewScopeMap.isEmpty()) |
| { |
| Map<String, Object> newViewScopeMap = newView.getViewMap(false); |
| if (newViewScopeMap == null) |
| { |
| newView.restoreViewScopeState(context, newViewState); |
| } |
| else |
| { |
| // Should theoretically not happen, because when a pooled static view |
| // is saved, the view scope map is skipped, otherwise it could be a |
| // leak. Anyway, we let this code here that overrides the values from |
| // the original map. |
| for (Map.Entry<String, Object> entry2 : viewScopeMap.entrySet()) |
| { |
| newViewScopeMap.put(entry2.getKey(), entry2.getValue()); |
| } |
| } |
| } |
| } |
| |
| // Update request view metadata to ensure resource list is restored as when the |
| // view was built on the first time. This ensures correct calculation of added |
| // resources by dynamic behavior. |
| RequestViewContext rcv = RequestViewContext.getCurrentInstance(context, newView, false); |
| if (rcv != null) |
| { |
| rcv.setRequestViewMetadata(metadata.getRequestViewMetadata().cloneInstance()); |
| } |
| else |
| { |
| RequestViewContext.setCurrentInstance(context, newView, |
| RequestViewContext.newInstance(metadata.getRequestViewMetadata().cloneInstance())); |
| } |
| } |
| finally |
| { |
| context.setProcessingEvents(oldProcessingEvents); |
| } |
| } |
| |
| public void storeViewStructureMetadata(FacesContext context, UIViewRoot root) |
| { |
| ViewPool viewPool = getViewPool(context, root); |
| if (viewPool != null) |
| { |
| FaceletState faceletState = (FaceletState) root.getAttributes().get( |
| ComponentSupport.FACELET_STATE_INSTANCE); |
| boolean isDynamic = faceletState != null ? faceletState.isDynamic() : false; |
| if (!isDynamic) |
| { |
| viewPool.storeStaticViewStructureMetadata(context, root, faceletState); |
| } |
| else |
| { |
| viewPool.storeDynamicViewStructureMetadata(context, root, faceletState); |
| } |
| } |
| } |
| |
| public ViewStructureMetadata retrieveViewStructureMetadata(FacesContext context, UIViewRoot root) |
| { |
| ViewPool viewPool = getViewPool(context, root); |
| if (viewPool != null) |
| { |
| FaceletState faceletState = (FaceletState) root.getAttributes().get( |
| ComponentSupport.FACELET_STATE_INSTANCE); |
| boolean isDynamic = faceletState != null ? faceletState.isDynamic() : false; |
| if (!isDynamic) |
| { |
| return viewPool.retrieveStaticViewStructureMetadata(context, root); |
| } |
| else |
| { |
| return viewPool.retrieveDynamicViewStructureMetadata(context, root, faceletState); |
| } |
| } |
| return null; |
| } |
| |
| public void pushResetableView(FacesContext context, UIViewRoot view, FaceletState faceletViewState) |
| { |
| ViewPool viewPool = getViewPool(context, view); |
| if (viewPool != null) |
| { |
| boolean isDynamic = faceletViewState != null ? faceletViewState.isDynamic() : false; |
| if (!isDynamic) |
| { |
| clearTransientAndNonFaceletComponentsForStaticView(context, view); |
| viewPool.pushStaticStructureView(context, view); |
| } |
| else |
| { |
| ViewStructureMetadata viewStructureMetadata = viewPool.retrieveDynamicViewStructureMetadata( |
| context, view, faceletViewState); |
| if (viewStructureMetadata != null) |
| { |
| clearTransientAndNonFaceletComponentsForDynamicView(context, view, viewStructureMetadata); |
| viewPool.pushDynamicStructureView(context, view, faceletViewState); |
| } |
| } |
| } |
| } |
| |
| public void pushPartialView(FacesContext context, UIViewRoot view, FaceletState faceletViewState, int count) |
| { |
| ViewPool viewPool = getViewPool(context, view); |
| |
| if (viewPool != null && viewPool.isWorthToRecycleThisView(context, view)) |
| { |
| ViewStructureMetadata viewStructureMetadata = null; |
| if (faceletViewState == null) |
| { |
| viewStructureMetadata = viewPool.retrieveStaticViewStructureMetadata(context, view); |
| } |
| else |
| { |
| viewStructureMetadata = viewPool.retrieveDynamicViewStructureMetadata( |
| context, view, faceletViewState); |
| } |
| if (viewStructureMetadata != null) |
| { |
| ClearPartialTreeContext ptc = new ClearPartialTreeContext(); |
| // add partial structure view to the map. |
| clearTransientAndRemoveNonResetableComponents(context, ptc, view, viewStructureMetadata); |
| int reusableCount = ptc.getCount(); |
| float factor = ((float) reusableCount) / ((float) count); |
| if (factor > 0.3f) |
| { |
| viewPool.pushPartialStructureView(context, view); |
| } |
| } |
| } |
| } |
| |
| protected void clearTransientAndNonFaceletComponentsForStaticView(FacesContext context, UIViewRoot root) |
| { |
| // In a static view, clear components that are both transient and non bound to any facelet tag handler |
| // is quite simple. Since the structure of the view is static, there is no need to check component resources. |
| clearTransientAndNonFaceletComponents(context, root); |
| } |
| |
| public void clearTransientAndNonFaceletComponentsForDynamicView(final FacesContext context, |
| final UIViewRoot root, final ViewStructureMetadata viewStructureMetadata) |
| { |
| //Scan children |
| int childCount = root.getChildCount(); |
| if (childCount > 0) |
| { |
| for (int i = 0; i < childCount; i++) |
| { |
| UIComponent child = root.getChildren().get(i); |
| if (child != null && child.isTransient() && |
| child.getAttributes().get(ComponentSupport.MARK_CREATED) == null) |
| { |
| root.getChildren().remove(i); |
| i--; |
| childCount--; |
| } |
| else |
| { |
| if (child.getChildCount() > 0 || !child.getFacets().isEmpty()) |
| { |
| clearTransientAndNonFaceletComponents(context, child); |
| } |
| } |
| } |
| } |
| |
| clearTransientAndNonFaceletComponentsForDynamicViewUIViewRootFacets( |
| context, root, viewStructureMetadata); |
| } |
| |
| private void clearTransientAndNonFaceletComponentsForDynamicViewUIViewRootFacets(final FacesContext context, |
| final UIViewRoot root, ViewStructureMetadata viewStructureMetadata) |
| { |
| |
| //Scan facets |
| if (root.getFacetCount() > 0) |
| { |
| Map<String, UIComponent> facets = root.getFacets(); |
| for (Iterator<UIComponent> itr = facets.values().iterator(); itr.hasNext();) |
| { |
| UIComponent fc = itr.next(); |
| if (fc != null && !(fc instanceof ComponentResourceContainer)) |
| { |
| if ( fc.isTransient() && |
| fc.getAttributes().get(ComponentSupport.MARK_CREATED) == null) |
| { |
| itr.remove(); |
| } |
| else |
| { |
| if (fc.getChildCount() > 0 || !fc.getFacets().isEmpty()) |
| { |
| clearTransientAndNonFaceletComponents(context, fc); |
| } |
| } |
| } |
| else if (fc != null) |
| { |
| // In a facet which is a ComponentResourceContainer instance, |
| // we need to check these two cases: |
| // 1. Resources relocated by facelets |
| // 2. Resources created by effect of a @ResourceDependency annotation |
| if (fc.getId() != null |
| && fc.getId().startsWith(FaceletCompositionContextImpl.JAVAX_FACES_LOCATION_PREFIX)) |
| { |
| String target = fc.getId().substring( |
| FaceletCompositionContextImpl.JAVAX_FACES_LOCATION_PREFIX.length()); |
| Map<String, List<ResourceDependency>> addedResources = |
| viewStructureMetadata.getRequestViewMetadata(). |
| getResourceDependencyAnnotations(context); |
| List<ResourceDependency> resourceDependencyList = (addedResources != null) ? |
| addedResources.get(target) : null; |
| |
| clearComponentResourceContainer(context, fc, resourceDependencyList); |
| } |
| } |
| } |
| } |
| } |
| |
| private void clearComponentResourceContainer(final FacesContext context, UIComponent component, |
| List<ResourceDependency> resourceDependencyList) |
| { |
| //Scan children |
| int childCount = component.getChildCount(); |
| if (childCount > 0) |
| { |
| for (int i = 0; i < childCount; i++) |
| { |
| UIComponent child = component.getChildren().get(i); |
| String id = (String) child.getAttributes().get(ComponentSupport.MARK_CREATED); |
| if (child != null && child.isTransient() && id == null) |
| { |
| //Remove both transient not facelets bound components |
| component.getChildren().remove(i); |
| i--; |
| childCount--; |
| } |
| else if (id != null) |
| { |
| // If it has an id set, it is a facelet component resource. |
| // The refresh algorithm take care of the cleanup. |
| } |
| /* |
| else if (!child.isTransient() && id != null && !faceletResources.contains(id)) |
| { |
| // check if the resource has a facelet tag in this "dynamic state", if not |
| // remove it. Really leave a component resource does not harm, but |
| // the objective is make this view as close as when if it is built as new. |
| component.getChildren().remove(i); |
| i--; |
| childCount--; |
| }*/ |
| else |
| { |
| // Check if the component instance was created using a @ResourceDependency annotation |
| Object[] rdk = (Object[]) child.getAttributes().get(RequestViewMetadata.RESOURCE_DEPENDENCY_KEY); |
| if (rdk != null) |
| { |
| boolean found = false; |
| String library = (String) rdk[0]; |
| String name = (String) rdk[1]; |
| if (resourceDependencyList != null) |
| { |
| for (ResourceDependency resource : resourceDependencyList) |
| { |
| if (library == null && resource.library() == null) |
| { |
| if (name != null && name.equals(resource.name())) |
| { |
| found = true; |
| break; |
| } |
| } |
| else |
| { |
| if (library != null && library.equals(resource.library()) && |
| name != null && name.equals(resource.name()) ) |
| { |
| found = true; |
| break; |
| } |
| } |
| } |
| } |
| if (!found) |
| { |
| //Remove it, because for this dynamic state it it does not exists. |
| component.getChildren().remove(i); |
| i--; |
| childCount--; |
| } |
| // If found just leave it. |
| } |
| else |
| { |
| if (child.getChildCount() > 0 || !child.getFacets().isEmpty()) |
| { |
| clearTransientAndNonFaceletComponents(context, child); |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| /** |
| * Clear all transient components not created by facelets algorithm. In this way, |
| * we ensure the component tree does not have any changes done after markInitialState. |
| * |
| * @param context |
| * @param component |
| */ |
| private void clearTransientAndNonFaceletComponents(final FacesContext context, final UIComponent component) |
| { |
| //Scan children |
| int childCount = component.getChildCount(); |
| if (childCount > 0) |
| { |
| for (int i = 0; i < childCount; i++) |
| { |
| UIComponent child = component.getChildren().get(i); |
| if (child != null && child.isTransient() && |
| child.getAttributes().get(ComponentSupport.MARK_CREATED) == null) |
| { |
| component.getChildren().remove(i); |
| i--; |
| childCount--; |
| } |
| else |
| { |
| if (child.getChildCount() > 0 || !child.getFacets().isEmpty()) |
| { |
| clearTransientAndNonFaceletComponents(context, child); |
| } |
| } |
| } |
| } |
| |
| //Scan facets |
| if (component.getFacetCount() > 0) |
| { |
| Map<String, UIComponent> facets = component.getFacets(); |
| for (Iterator<UIComponent> itr = facets.values().iterator(); itr.hasNext();) |
| { |
| UIComponent fc = itr.next(); |
| if (fc != null && fc.isTransient() && |
| fc.getAttributes().get(ComponentSupport.MARK_CREATED) == null) |
| { |
| itr.remove(); |
| } |
| else |
| { |
| if (fc.getChildCount() > 0 || !fc.getFacets().isEmpty()) |
| { |
| clearTransientAndNonFaceletComponents(context, fc); |
| } |
| } |
| } |
| } |
| } |
| |
| private void clearTransientAndRemoveNonResetableComponents(final FacesContext context, |
| final ClearPartialTreeContext ptc, final UIViewRoot root, |
| ViewStructureMetadata viewStructureMetadata) |
| { |
| //Scan children |
| int childCount = root.getChildCount(); |
| |
| try |
| { |
| root.getAttributes().put(ViewPoolProcessor.RESET_SAVE_STATE_MODE_KEY, ViewPoolProcessor.RESET_MODE_HARD); |
| if (childCount > 0) |
| { |
| for (int i = 0; i < childCount; i++) |
| { |
| UIComponent child = root.getChildren().get(i); |
| boolean containsFaceletId = child.getAttributes().containsKey(ComponentSupport.MARK_CREATED); |
| if (child != null && child.isTransient() && !containsFaceletId) |
| { |
| //Transient and not bound to facelets tag, remove it!. |
| root.getChildren().remove(i); |
| i--; |
| childCount--; |
| } |
| else |
| { |
| if (child.getAttributes().containsKey(ComponentSupport.COMPONENT_ADDED_BY_HANDLER_MARKER)) |
| { |
| //Dynamically added or moved, remove it! |
| root.getChildren().remove(i); |
| i--; |
| childCount--; |
| |
| } |
| else if (containsFaceletId || |
| child.getAttributes().containsKey(ComponentSupport.COMPONENT_ADDED_BY_HANDLER_MARKER)) |
| { |
| // Bound to a facelet tag or created by facelets, we have two options: |
| // 1. If is not transient, check its state and try to clear it, if fails remove it |
| // 2. If is transient, assume stateless, continue. |
| if (!child.isTransient()) |
| { |
| // Remember that hard reset is already enabled. |
| Object state = child.saveState(context); |
| if (state == null) |
| { |
| if (child.getChildCount() > 0 || !child.getFacets().isEmpty()) |
| { |
| clearTransientAndRemoveNonResetableComponents(context, ptc, child); |
| } |
| ptc.incrementCount(); |
| } |
| else |
| { |
| root.getChildren().remove(i); |
| i--; |
| childCount--; |
| } |
| } |
| else |
| { |
| ptc.incrementCount(); |
| } |
| } |
| else |
| { |
| // Non facelets component, remove it!. |
| root.getChildren().remove(i); |
| i--; |
| childCount--; |
| } |
| } |
| } |
| } |
| |
| clearTransientAndNonFaceletComponentsForDynamicViewUIViewRootFacets( |
| context, root, viewStructureMetadata); |
| } |
| finally |
| { |
| root.getAttributes().put(ViewPoolProcessor.RESET_SAVE_STATE_MODE_KEY, |
| ViewPoolProcessor.RESET_MODE_OFF); |
| } |
| } |
| |
| private void clearTransientAndRemoveNonResetableComponents(final FacesContext context, |
| final ClearPartialTreeContext ptc, final UIComponent component) |
| { |
| //Scan children |
| int childCount = component.getChildCount(); |
| if (childCount > 0) |
| { |
| for (int i = 0; i < childCount; i++) |
| { |
| UIComponent child = component.getChildren().get(i); |
| boolean containsFaceletId = child.getAttributes().containsKey(ComponentSupport.MARK_CREATED); |
| if (child.isTransient() && !containsFaceletId) |
| { |
| //Transient and not bound to facelets tag, remove it!. |
| component.getChildren().remove(i); |
| i--; |
| childCount--; |
| } |
| else |
| { |
| if (child.getAttributes().containsKey(ComponentSupport.COMPONENT_ADDED_BY_HANDLER_MARKER)) |
| { |
| //Dynamically added or moved, remove it! |
| component.getChildren().remove(i); |
| i--; |
| childCount--; |
| |
| } |
| else if (containsFaceletId || |
| child.getAttributes().containsKey(ComponentSupport.COMPONENT_ADDED_BY_HANDLER_MARKER)) |
| { |
| // Bound to a facelet tag or created by facelets, we have two options: |
| // 1. If is not transient, check its state and try to clear it, if fails remove it |
| // 2. If is transient, assume stateless, continue. |
| if (!child.isTransient()) |
| { |
| // Remember that hard reset is already enabled. |
| Object state = child.saveState(context); |
| if (state == null) |
| { |
| if (child.getChildCount() > 0 || !child.getFacets().isEmpty()) |
| { |
| clearTransientAndRemoveNonResetableComponents(context, ptc, child); |
| } |
| ptc.incrementCount(); |
| } |
| else |
| { |
| component.getChildren().remove(i); |
| i--; |
| childCount--; |
| } |
| } |
| else |
| { |
| ptc.incrementCount(); |
| } |
| } |
| else |
| { |
| // Non facelets component, remove it!. |
| component.getChildren().remove(i); |
| i--; |
| childCount--; |
| } |
| } |
| } |
| } |
| |
| //Scan facets |
| if (component.getFacetCount() > 0) |
| { |
| Map<String, UIComponent> facets = component.getFacets(); |
| for (Iterator<UIComponent> itr = facets.values().iterator(); itr.hasNext();) |
| { |
| UIComponent fc = itr.next(); |
| boolean containsFaceletId = fc.getAttributes().containsKey(ComponentSupport.MARK_CREATED); |
| if (fc.isTransient() && !containsFaceletId) |
| { |
| //Transient and not bound to facelets tag, remove it!. |
| itr.remove(); |
| } |
| else |
| { |
| if (fc.getAttributes().containsKey(ComponentSupport.COMPONENT_ADDED_BY_HANDLER_MARKER)) |
| { |
| //Dynamically added or moved, remove it! |
| itr.remove(); |
| |
| } |
| else if (containsFaceletId || |
| fc.getAttributes().containsKey(ComponentSupport.COMPONENT_ADDED_BY_HANDLER_MARKER)) |
| { |
| // Bound to a facelet tag or created by facelets, we have two options: |
| // 1. If is not transient, check its state and try to clear it, if fails remove it |
| // 2. If is transient, assume stateless, continue. |
| if (!fc.isTransient()) |
| { |
| // Remember that hard reset is already enabled. |
| Object state = fc.saveState(context); |
| if (state == null) |
| { |
| if (fc.getChildCount() > 0 || !fc.getFacets().isEmpty()) |
| { |
| clearTransientAndRemoveNonResetableComponents(context, ptc, fc); |
| } |
| ptc.incrementCount(); |
| } |
| else |
| { |
| itr.remove(); |
| } |
| } |
| else |
| { |
| ptc.incrementCount(); |
| } |
| } |
| else |
| { |
| // Non facelets component, remove it!. |
| itr.remove(); |
| } |
| } |
| } |
| } |
| } |
| |
| public void processDeferredNavigation(FacesContext facesContext) |
| { |
| Object[] command = (Object[]) facesContext.getAttributes().get( |
| ViewPoolProcessor.INVOKE_DEFERRED_NAVIGATION); |
| if (command != null) |
| { |
| try |
| { |
| facesContext.getAttributes().put(ViewPoolProcessor.DISPOSE_VIEW_NAVIGATION, Boolean.TRUE); |
| NavigationHandler navigationHandler = facesContext.getApplication().getNavigationHandler(); |
| if (command.length == 3) |
| { |
| navigationHandler.handleNavigation(facesContext, (String) command[0], (String) command[1], |
| (String) command[2]); |
| } |
| else |
| { |
| navigationHandler.handleNavigation(facesContext, (String) command[0], (String) command[1]); |
| } |
| //Render Response if needed |
| facesContext.renderResponse(); |
| facesContext.getAttributes().remove(ViewPoolProcessor.INVOKE_DEFERRED_NAVIGATION); |
| } |
| finally |
| { |
| facesContext.getAttributes().remove(ViewPoolProcessor.DISPOSE_VIEW_NAVIGATION); |
| } |
| } |
| } |
| |
| public void disposeView(FacesContext facesContext, UIViewRoot root) |
| { |
| if (root == null) |
| { |
| return; |
| } |
| |
| String viewId = root.getViewId(); |
| if (viewId == null) |
| { |
| return; |
| } |
| Application app = facesContext.getApplication(); |
| if (app == null) |
| { |
| return; |
| } |
| ViewHandler viewHandler = app.getViewHandler(); |
| if (viewHandler == null) |
| { |
| return; |
| } |
| |
| if (Boolean.TRUE.equals(facesContext.getAttributes().get(ViewPoolProcessor.DISPOSE_VIEW_NAVIGATION))) |
| { |
| ViewDeclarationLanguage vdl = facesContext.getApplication(). |
| getViewHandler().getViewDeclarationLanguage( |
| facesContext, root.getViewId()); |
| |
| if (vdl != null && ViewDeclarationLanguage.FACELETS_VIEW_DECLARATION_LANGUAGE_ID.equals(vdl.getId())) |
| { |
| StateManagementStrategy sms = vdl.getStateManagementStrategy(facesContext, root.getId()); |
| if (sms != null) |
| { |
| // Force indirectly to store the map in the pool |
| facesContext.getAttributes().put(ViewPoolProcessor.FORCE_HARD_RESET, Boolean.TRUE); |
| |
| try |
| { |
| Object state = sms.saveView(facesContext); |
| } |
| finally |
| { |
| facesContext.getAttributes().remove(ViewPoolProcessor.FORCE_HARD_RESET); |
| } |
| |
| // Clear the calculated value from the application map |
| facesContext.getAttributes().remove(StateManagerImpl.SERIALIZED_VIEW_REQUEST_ATTR); |
| } |
| } |
| } |
| } |
| |
| private static class ClearPartialTreeContext |
| { |
| private int count; |
| |
| public ClearPartialTreeContext() |
| { |
| count = 0; |
| } |
| |
| public int getCount() |
| { |
| return count; |
| } |
| |
| public int incrementCount() |
| { |
| return count++; |
| } |
| |
| public void setCount(int count) |
| { |
| this.count = count; |
| } |
| } |
| } |