| /* |
| * 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.io.Serializable; |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import javax.el.ValueExpression; |
| import javax.faces.FacesException; |
| import javax.faces.FactoryFinder; |
| import javax.faces.application.ProjectStage; |
| import javax.faces.application.StateManager; |
| import javax.faces.component.ContextCallback; |
| import javax.faces.component.UIComponent; |
| import javax.faces.component.UIComponentBase; |
| import javax.faces.component.UIViewRoot; |
| import javax.faces.component.visit.VisitCallback; |
| import javax.faces.component.visit.VisitContext; |
| import javax.faces.component.visit.VisitContextFactory; |
| import javax.faces.component.visit.VisitResult; |
| import javax.faces.context.FacesContext; |
| import javax.faces.event.PostAddToViewEvent; |
| import javax.faces.event.PreRemoveFromViewEvent; |
| import javax.faces.event.SystemEvent; |
| import javax.faces.event.SystemEventListener; |
| import javax.faces.render.RenderKitFactory; |
| import javax.faces.render.ResponseStateManager; |
| import javax.faces.view.StateManagementStrategy; |
| import javax.faces.view.ViewDeclarationLanguage; |
| import javax.faces.view.ViewDeclarationLanguageFactory; |
| import javax.faces.view.ViewMetadata; |
| import org.apache.myfaces.application.StateManagerImpl; |
| import org.apache.myfaces.context.RequestViewContext; |
| import org.apache.myfaces.config.MyfacesConfig; |
| import org.apache.myfaces.util.lang.ClassUtils; |
| import org.apache.myfaces.util.lang.HashMapUtils; |
| import org.apache.myfaces.component.visit.MyFacesVisitHints; |
| import org.apache.myfaces.view.facelets.compiler.CheckDuplicateIdFaceletUtils; |
| import org.apache.myfaces.view.facelets.pool.ViewEntry; |
| import org.apache.myfaces.view.facelets.pool.ViewPool; |
| import org.apache.myfaces.view.facelets.pool.ViewStructureMetadata; |
| import org.apache.myfaces.view.facelets.tag.jsf.ComponentSupport; |
| import org.apache.myfaces.view.facelets.tag.jsf.FaceletState; |
| |
| /** |
| * This class implements partial state saving feature when facelets |
| * is used to render pages. (Theorically it could be applied on jsp case too, |
| * but all considerations below should be true before apply it). |
| * |
| * The following considerations apply for this class: |
| * |
| * 1. This StateManagementStrategy should only be active if javax.faces.PARTIAL_STATE_SAVING |
| * config param is active(true). See javadoc on StateManager for details. |
| * 2. A map using component clientId as keys are used to hold the state. |
| * 3. Each component has a valid id after ViewDeclarationLanguage.buildView(). |
| * This implies that somewhere, every TagHandler that create an UIComponent |
| * instance should call setId and assign it. |
| * 4. Every TagHandler that create an UIComponent instance should call markInitialState |
| * after the component is populated. Otherwise, full state is always saved. |
| * 5. A SystemEventListener is used to keep track for added and removed components, listen |
| * PostAddToViewEvent and PreRemoveFromViewEvent event triggered by UIComponent.setParent() |
| * method. |
| * 6. It is not possible to use javax.faces.component.visit API to traverse the component |
| * tree during save/restore, because UIData.visitTree traverse all rows and we only need |
| * to restore state per component (not per row). |
| * 7. It is necessary to preserve the order of the children added/removed between requests. |
| * 8. Added and removed components could be seen as subtrees. This imply that we need to save |
| * the structure of the added components subtree and remove one component could be remove |
| * all its children and facets from view inclusive. |
| * 9. It is necessary to save and restore the list of added/removed components between several |
| * requests. |
| * 10.All components ids removed in any moment of time must be preserved. |
| * 11.Each component must be restored only once. |
| * 11.The order is important for ids added when it is traversed the tree, otherwise the algorithm |
| * could change the order in which components will be restored. |
| * |
| * @author Leonardo Uribe (latest modification by $Author$) |
| * @version $Revision$ $Date$ |
| * @since 2.0 |
| * |
| */ |
| public class DefaultFaceletsStateManagementStrategy extends StateManagementStrategy |
| { |
| public static final String CLIENTIDS_ADDED = "oam.CLIENTIDS_ADDED"; |
| |
| public static final String CLIENTIDS_REMOVED = "oam.CLIENTIDS_REMOVED"; |
| |
| /** |
| * Key used on component attribute map to indicate if a component was added |
| * after build view, so itself and all descendants should not use partial |
| * state saving. There are two possible values: |
| * |
| * Key not present: The component uses pss. |
| * ComponentState.ADD: The component was added to the view after build view. |
| * ComponentState.REMOVE_ADD: The component was removed/added to the view. Itself and all |
| * descendants should be saved and restored, but we have to unregister/register |
| * from CLIENTIDS_ADDED and CLIENTIDS_REMOVED lists. See ComponentSupport.markComponentToRestoreFully |
| * for details. |
| * ComponentState.ADDED: The component has been added or removed/added, but it has |
| * been already processed. |
| */ |
| public static final String COMPONENT_ADDED_AFTER_BUILD_VIEW = "oam.COMPONENT_ADDED_AFTER_BUILD_VIEW"; |
| |
| |
| private static final Object[] EMPTY_STATES = new Object[]{null, null}; |
| |
| private static final String UNIQUE_ID_COUNTER_KEY = |
| "oam.view.uniqueIdCounter"; |
| |
| private ViewDeclarationLanguageFactory _vdlFactory; |
| |
| private RenderKitFactory _renderKitFactory = null; |
| |
| private VisitContextFactory _visitContextFactory = null; |
| |
| private String checkIdsProductionMode; |
| |
| private ViewPoolProcessor _viewPoolProcessor; |
| |
| public DefaultFaceletsStateManagementStrategy() |
| { |
| this(FacesContext.getCurrentInstance()); |
| } |
| |
| public DefaultFaceletsStateManagementStrategy(FacesContext context) |
| { |
| _vdlFactory = (ViewDeclarationLanguageFactory) |
| FactoryFinder.getFactory(FactoryFinder.VIEW_DECLARATION_LANGUAGE_FACTORY); |
| _viewPoolProcessor = ViewPoolProcessor.getInstance(context); |
| checkIdsProductionMode = MyfacesConfig.getCurrentInstance(context).getCheckIdProductionMode(); |
| } |
| |
| @SuppressWarnings("unchecked") |
| @Override |
| public UIViewRoot restoreView (FacesContext context, String viewId, String renderKitId) |
| { |
| Map<String, Object> states; |
| UIViewRoot view = null; |
| |
| ResponseStateManager manager = |
| getRenderKitFactory().getRenderKit(context, renderKitId).getResponseStateManager(); |
| |
| // The value returned here is expected to be false (set by RestoreViewExecutor), but |
| //we don't know if some ViewHandler wrapper could change it, so it is better to save the value. |
| final boolean oldContextEventState = context.isProcessingEvents(); |
| |
| // Get previous state from ResponseStateManager. |
| Object[] state = (Object[]) manager.getState(context, viewId); |
| if (state == null) |
| { |
| //No state could be restored, return null causing ViewExpiredException |
| return null; |
| } |
| |
| if (state[1] instanceof Object[]) |
| { |
| Object[] fullState = (Object[]) state[1]; |
| view = (UIViewRoot) internalRestoreTreeStructure((TreeStructComponent)fullState[0]); |
| |
| if (view != null) |
| { |
| context.setViewRoot (view); |
| view.processRestoreState(context, fullState[1]); |
| |
| // If the view is restored fully, it is necessary to refresh RequestViewContext, otherwise at |
| // each ajax request new components associated with @ResourceDependency annotation will be added |
| // to the tree, making the state bigger without real need. |
| RequestViewContext.getCurrentInstance(context). |
| refreshRequestViewContext(context, view); |
| |
| if (fullState.length == 3 && fullState[2] != null) |
| { |
| context.setResourceLibraryContracts((List) UIComponentBase. |
| restoreAttachedState(context, fullState[2])); |
| } |
| } |
| } |
| else |
| { |
| // Per the spec: build the view. |
| ViewDeclarationLanguage vdl = _vdlFactory.getViewDeclarationLanguage(viewId); |
| Object faceletViewState = null; |
| try |
| { |
| ViewMetadata metadata = vdl.getViewMetadata (context, viewId); |
| |
| if (metadata != null) |
| { |
| view = metadata.createMetadataView(context); |
| |
| // If no view and response complete there is no need to continue |
| if (view == null && context.getResponseComplete()) |
| { |
| return null; |
| } |
| } |
| if (view == null) |
| { |
| view = context.getApplication().getViewHandler().createView(context, viewId); |
| } |
| |
| context.setViewRoot(view); |
| boolean skipBuildView = false; |
| if (state[1] != null) |
| { |
| // Since JSF 2.2, UIViewRoot.restoreViewScopeState() must be called, but |
| // to get the state of the root, it is necessary to force calculate the |
| // id from this location. Remember in this point, PSS is enabled, so the |
| // code match with the assigment done in |
| // FaceletViewDeclarationLanguage.buildView() |
| states = (Map<String, Object>) state[1]; |
| faceletViewState = UIComponentBase.restoreAttachedState( |
| context,states.get(ComponentSupport.FACELET_STATE_INSTANCE)); |
| if (faceletViewState != null && _viewPoolProcessor != null) |
| { |
| ViewPool viewPool = _viewPoolProcessor.getViewPool(context, view); |
| if (viewPool != null) |
| { |
| ViewStructureMetadata viewMetadata = viewPool.retrieveDynamicViewStructureMetadata( |
| context, view, (FaceletState) faceletViewState); |
| if (viewMetadata != null) |
| { |
| ViewEntry entry = viewPool.popDynamicStructureView(context, view, |
| (FaceletState) faceletViewState); |
| if (entry != null) |
| { |
| skipBuildView = true; |
| _viewPoolProcessor.cloneAndRestoreView(context, view, entry, viewMetadata); |
| } |
| } |
| } |
| } |
| if (view.getId() == null) |
| { |
| view.setId(view.createUniqueId(context, null)); |
| } |
| if (faceletViewState != null) |
| { |
| FaceletState newFaceletState = (FaceletState) view.getAttributes().get( |
| ComponentSupport.FACELET_STATE_INSTANCE); |
| if (newFaceletState != null) |
| { |
| newFaceletState.restoreState(context, |
| ((FaceletState)faceletViewState).saveState(context)); |
| faceletViewState = newFaceletState; |
| } |
| else |
| { |
| view.getAttributes().put(ComponentSupport.FACELET_STATE_INSTANCE, faceletViewState); |
| } |
| } |
| if (state.length == 3) |
| { |
| //Jump to where the count is |
| view.getAttributes().put(UNIQUE_ID_COUNTER_KEY, state[2]); |
| } |
| Object viewRootState = states.get(view.getClientId(context)); |
| if (viewRootState != null) |
| { |
| try |
| { |
| view.pushComponentToEL(context, view); |
| view.restoreViewScopeState(context, viewRootState); |
| } |
| finally |
| { |
| view.popComponentFromEL(context); |
| } |
| } |
| } |
| // On RestoreViewExecutor, setProcessingEvents is called first to false |
| // and then to true when postback. Since we need listeners registered to PostAddToViewEvent |
| // event to be handled, we should enable it again. For partial state saving we need this listeners |
| // be called from here and relocate components properly. |
| if (!skipBuildView) |
| { |
| try |
| { |
| context.setProcessingEvents (true); |
| vdl.buildView(context, view); |
| // In the latest code related to PostAddToView, it is |
| // triggered no matter if it is applied on postback. It seems that MYFACES-2389, |
| // TRINIDAD-1670 and TRINIDAD-1671 are related. |
| suscribeListeners(view); |
| } |
| finally |
| { |
| context.setProcessingEvents (oldContextEventState); |
| } |
| } |
| } |
| catch (Throwable e) |
| { |
| throw new FacesException ("unable to create view \"" + viewId + '"', e); |
| } |
| |
| // Stateless mode only for transient views and non stateless mode for |
| // stateful views. This check avoid apply state over a stateless view. |
| boolean statelessMode = manager.isStateless(context, viewId); |
| if (statelessMode && !view.isTransient()) |
| { |
| throw new IllegalStateException("View is not transient"); |
| } |
| if (!statelessMode && view.isTransient()) |
| { |
| throw new IllegalStateException("Cannot apply state over stateless view"); |
| } |
| |
| if (state[1] != null) |
| { |
| states = (Map<String, Object>) state[1]; |
| //Save the last unique id counter key in UIViewRoot |
| Integer lastUniqueIdCounter = (Integer) view.getAttributes().get(UNIQUE_ID_COUNTER_KEY); |
| // Retrieve the facelet state before restore anything. The reason is |
| // it could be necessary to restore the bindings map from here. |
| FaceletState oldFaceletState = (FaceletState) view.getAttributes().get( |
| ComponentSupport.FACELET_STATE_INSTANCE); |
| |
| // Visit the children and restore their state. |
| boolean emptyState = false; |
| boolean containsFaceletState = states.containsKey(ComponentSupport.FACELET_STATE_INSTANCE); |
| if (states.isEmpty()) |
| { |
| emptyState = true; |
| } |
| else if (states.size() == 1 && containsFaceletState) |
| { |
| emptyState = true; |
| } |
| |
| //Restore state of current components |
| if (!emptyState) |
| { |
| // Check if there is only one component state |
| // and that state is UIViewRoot instance (for example when using ViewScope) |
| if ((states.size() == 1 && !containsFaceletState) |
| || (states.size() == 2 && containsFaceletState)) |
| { |
| Object viewState = states.get(view.getClientId(context)); |
| if (viewState != null) |
| { |
| restoreViewRootOnlyFromMap(context,viewState, view); |
| } |
| else |
| { |
| //The component is not viewRoot, restore as usual. |
| restoreStateFromMap(context, states, view); |
| } |
| } |
| else |
| { |
| restoreStateFromMap(context, states, view); |
| } |
| } |
| if (faceletViewState != null) |
| { |
| // Make sure binding map |
| if (oldFaceletState != null && oldFaceletState.getBindings() != null |
| && !oldFaceletState.getBindings().isEmpty()) |
| { |
| // Be sure the new facelet state has the binding map filled from the old one. |
| // When vdl.buildView() is called by restoreView, FaceletState.bindings map is filled, but |
| // when view pool is enabled, vdl.buildView() could restore the view, but create an alternate |
| // FaceletState instance, different from the one restored. In this case, the restored instance |
| // has precedence, but we need to fill bindings map using the entries from the instance that |
| // comes from the view pool. |
| FaceletState newFaceletState = (FaceletState) faceletViewState; |
| for (Map.Entry<String, Map<String, ValueExpression>> entry : |
| oldFaceletState.getBindings().entrySet()) |
| { |
| for (Map.Entry<String, ValueExpression> entry2 : entry.getValue().entrySet()) |
| { |
| ValueExpression expr = newFaceletState.getBinding(entry.getKey(), entry2.getKey()); |
| if (expr == null) |
| { |
| newFaceletState.putBinding(entry.getKey(), entry2.getKey(), entry2.getValue()); |
| } |
| } |
| } |
| view.getAttributes().put(ComponentSupport.FACELET_STATE_INSTANCE, newFaceletState); |
| } |
| else |
| { |
| //restore bindings |
| view.getAttributes().put(ComponentSupport.FACELET_STATE_INSTANCE, faceletViewState); |
| } |
| } |
| if (lastUniqueIdCounter != null) |
| { |
| Integer newUniqueIdCounter = (Integer) view.getAttributes().get(UNIQUE_ID_COUNTER_KEY); |
| if (newUniqueIdCounter != null && lastUniqueIdCounter > newUniqueIdCounter) |
| { |
| // The unique counter was restored by a side effect of |
| // restoreState() over UIViewRoot with a lower count, |
| // to avoid a component duplicate id exception we need to fix the count. |
| view.getAttributes().put(UNIQUE_ID_COUNTER_KEY, lastUniqueIdCounter); |
| } |
| } |
| handleDynamicAddedRemovedComponents(context, view, states); |
| } |
| } |
| return view; |
| } |
| |
| public void handleDynamicAddedRemovedComponents(FacesContext context, UIViewRoot view, Map<String, Object> states) |
| { |
| List<String> clientIdsRemoved = getClientIdsRemoved(view); |
| if (clientIdsRemoved != null) |
| { |
| Set<String> idsRemovedSet = new HashSet<>(HashMapUtils.calcCapacity(clientIdsRemoved.size())); |
| context.getAttributes().put(FaceletViewDeclarationLanguage.REMOVING_COMPONENTS_BUILD, Boolean.TRUE); |
| try |
| { |
| RemoveComponentCallback removeCallback = null; |
| |
| // perf: clientIds are ArrayList: see method registerOnAddRemoveList(String) |
| for (int i = 0, size = clientIdsRemoved.size(); i < size; i++) |
| { |
| String clientId = clientIdsRemoved.get(i); |
| if (!idsRemovedSet.contains(clientId)) |
| { |
| if (removeCallback == null) |
| { |
| removeCallback = new RemoveComponentCallback(); |
| } |
| removeCallback.setComponentFound(false); |
| |
| view.invokeOnComponent(context, clientId, removeCallback); |
| if (removeCallback.isComponentFound()) |
| { |
| //Add only if component found |
| idsRemovedSet.add(clientId); |
| } |
| } |
| } |
| clientIdsRemoved.clear(); |
| clientIdsRemoved.addAll(idsRemovedSet); |
| } |
| finally |
| { |
| context.getAttributes().remove(FaceletViewDeclarationLanguage.REMOVING_COMPONENTS_BUILD); |
| } |
| } |
| |
| |
| List<String> clientIdsAdded = getClientIdsAdded(view); |
| if (clientIdsAdded != null) |
| { |
| Set<String> idsAddedSet = new HashSet<>(HashMapUtils.calcCapacity(clientIdsAdded.size())); |
| |
| AddComponentCallback addCallback = null; |
| |
| // perf: clientIds are ArrayList: see method setClientsIdsAdded(String) |
| for (int i = 0, size = clientIdsAdded.size(); i < size; i++) |
| { |
| String clientId = clientIdsAdded.get(i); |
| if (!idsAddedSet.contains(clientId)) |
| { |
| final AttachedFullStateWrapper wrapper = (AttachedFullStateWrapper) states.get(clientId); |
| if (wrapper != null) |
| { |
| final Object[] addedState = (Object[]) wrapper.getWrappedStateObject(); |
| if (addedState != null) |
| { |
| if (addedState.length == 2) |
| { |
| view = (UIViewRoot) |
| internalRestoreTreeStructure((TreeStructComponent) addedState[0]); |
| view.processRestoreState(context, addedState[1]); |
| break; |
| } |
| else |
| { |
| if (addCallback == null) |
| { |
| addCallback = new AddComponentCallback(); |
| } |
| addCallback.setAddedState(addedState); |
| |
| final String parentClientId = (String) addedState[0]; |
| view.invokeOnComponent(context, parentClientId, addCallback); |
| } |
| } |
| } |
| idsAddedSet.add(clientId); |
| } |
| } |
| |
| // Reset this list, because it will be calculated later when the view is being saved |
| // in the right order, preventing duplicates (see COMPONENT_ADDED_AFTER_BUILD_VIEW for details). |
| clientIdsAdded.clear(); |
| |
| // This call only has sense when components has been added programatically, because if facelets has control |
| // over all components in the component tree, build the initial state and apply the state will have the |
| // same effect. |
| RequestViewContext.getCurrentInstance(context).refreshRequestViewContext(context, view); |
| } |
| } |
| |
| public static class RemoveComponentCallback implements ContextCallback |
| { |
| private boolean componentFound; |
| |
| public RemoveComponentCallback() |
| { |
| this.componentFound = false; |
| } |
| |
| @Override |
| public void invokeContextCallback(FacesContext context, UIComponent target) |
| { |
| if (target.getParent() != null && !target.getParent().getChildren().remove(target)) |
| { |
| String key = null; |
| if (target.getParent().getFacetCount() > 0) |
| { |
| for (Map.Entry<String, UIComponent> entry : target.getParent().getFacets().entrySet()) |
| { |
| if (entry.getValue() == target) |
| { |
| key = entry.getKey(); |
| break; |
| } |
| } |
| } |
| if (key != null) |
| { |
| UIComponent removedTarget = target.getParent().getFacets().remove(key); |
| if (removedTarget != null) |
| { |
| this.componentFound = true; |
| } |
| } |
| } |
| else |
| { |
| this.componentFound = true; |
| } |
| } |
| |
| public boolean isComponentFound() |
| { |
| return componentFound; |
| } |
| |
| public void setComponentFound(boolean componentFound) |
| { |
| this.componentFound = componentFound; |
| } |
| } |
| |
| public static class AddComponentCallback implements ContextCallback |
| { |
| private Object[] addedState; |
| |
| public AddComponentCallback() |
| { |
| } |
| |
| @Override |
| public void invokeContextCallback(FacesContext context, UIComponent target) |
| { |
| if (addedState[1] != null) |
| { |
| String facetName = (String) addedState[1]; |
| UIComponent child = internalRestoreTreeStructure((TreeStructComponent) addedState[3]); |
| child.processRestoreState(context, addedState[4]); |
| target.getFacets().put(facetName,child); |
| } |
| else |
| { |
| Integer childIndex = (Integer) addedState[2]; |
| UIComponent child = internalRestoreTreeStructure((TreeStructComponent)addedState[3]); |
| child.processRestoreState(context, addedState[4]); |
| |
| boolean done = false; |
| // Is the child a facelet controlled component? |
| if (child.getAttributes().containsKey(ComponentSupport.MARK_CREATED)) |
| { |
| // By effect of c:forEach it is possible that the component can be duplicated |
| // in the component tree, so what we need to do as a fallback is replace the |
| // component in the spot with the restored version. |
| UIComponent parent = target; |
| if (parent.getChildCount() > 0) |
| { |
| String tagId = (String) child.getAttributes().get(ComponentSupport.MARK_CREATED); |
| if (childIndex < parent.getChildCount()) |
| { |
| // Try to find the component quickly |
| UIComponent dup = parent.getChildren().get(childIndex); |
| if (tagId.equals(dup.getAttributes().get(ComponentSupport.MARK_CREATED))) |
| { |
| // Replace |
| parent.getChildren().remove(childIndex.intValue()); |
| parent.getChildren().add(childIndex, child); |
| done = true; |
| } |
| } |
| if (!done) |
| { |
| // Fallback to iteration |
| for (int i = 0, childCount = parent.getChildCount(); i < childCount; i ++) |
| { |
| UIComponent dup = parent.getChildren().get(i); |
| if (tagId.equals(dup.getAttributes().get(ComponentSupport.MARK_CREATED))) |
| { |
| // Replace |
| parent.getChildren().remove(i); |
| parent.getChildren().add(i, child); |
| done = true; |
| break; |
| } |
| } |
| } |
| } |
| } |
| if (!done) |
| { |
| try |
| { |
| target.getChildren().add(childIndex, child); |
| } |
| catch (IndexOutOfBoundsException e) |
| { |
| // We can't be sure about where should be this |
| // item, so just add it. |
| target.getChildren().add(child); |
| } |
| } |
| } |
| } |
| |
| public Object[] getAddedState() |
| { |
| return addedState; |
| } |
| |
| public void setAddedState(Object[] addedState) |
| { |
| this.addedState = addedState; |
| } |
| } |
| |
| @Override |
| public Object saveView(FacesContext context) |
| { |
| UIViewRoot view = context.getViewRoot(); |
| Object states; |
| |
| if (view == null) |
| { |
| // Not much that can be done. |
| return null; |
| } |
| |
| Object serializedView = context.getAttributes().get(StateManagerImpl.SERIALIZED_VIEW_REQUEST_ATTR); |
| |
| //Note on ajax case the method saveState could be called twice: once before start |
| //document rendering and the other one when it is called StateManager.getViewState method. |
| if (serializedView == null) |
| { |
| // Make sure the client IDs are unique per the spec. |
| if (context.isProjectStage(ProjectStage.Production)) |
| { |
| if (MyfacesConfig.CHECK_ID_PRODUCTION_MODE_AUTO.equals(checkIdsProductionMode)) |
| { |
| CheckDuplicateIdFaceletUtils.checkIdsStatefulComponents(context, view); |
| } |
| else if (MyfacesConfig.CHECK_ID_PRODUCTION_MODE_TRUE.equals(checkIdsProductionMode)) |
| { |
| CheckDuplicateIdFaceletUtils.checkIds(context, view); |
| } |
| } |
| else |
| { |
| CheckDuplicateIdFaceletUtils.checkIds(context, view); |
| } |
| |
| // Create save state objects for every component. |
| |
| boolean viewResetable = false; |
| int count = 0; |
| Object faceletViewState = null; |
| boolean saveViewFully = view.getAttributes().containsKey(COMPONENT_ADDED_AFTER_BUILD_VIEW); |
| if (saveViewFully) |
| { |
| ensureClearInitialState(view); |
| Object rlcStates = !context.getResourceLibraryContracts().isEmpty() ? |
| UIComponentBase.saveAttachedState(context, |
| new ArrayList<>(context.getResourceLibraryContracts())) : null; |
| states = new Object[]{ |
| internalBuildTreeStructureToSave(view), |
| view.processSaveState(context), rlcStates}; |
| } |
| else |
| { |
| states = new HashMap<>(); |
| |
| faceletViewState = view.getAttributes().get(ComponentSupport.FACELET_STATE_INSTANCE); |
| if (faceletViewState != null) |
| { |
| ((Map<String, Object>) states).put(ComponentSupport.FACELET_STATE_INSTANCE, |
| UIComponentBase.saveAttachedState(context, faceletViewState)); |
| //Do not save on UIViewRoot |
| view.getAttributes().remove(ComponentSupport.FACELET_STATE_INSTANCE); |
| view.getTransientStateHelper().putTransient( |
| ComponentSupport.FACELET_STATE_INSTANCE, faceletViewState); |
| } |
| if (_viewPoolProcessor != null |
| && _viewPoolProcessor.isViewPoolEnabledForThisView(context, view)) |
| { |
| SaveStateAndResetViewCallback cb = saveStateOnMapVisitTreeAndReset( |
| context, |
| (Map<String,Object>) states, |
| view, |
| Boolean.TRUE.equals(context.getAttributes().get(ViewPoolProcessor.FORCE_HARD_RESET))); |
| viewResetable = cb.isViewResetable(); |
| count = cb.getCount(); |
| } |
| else |
| { |
| saveStateOnMapVisitTree(context,(Map<String,Object>) states, view); |
| } |
| |
| if (((Map<String,Object>) states).isEmpty()) |
| { |
| states = null; |
| } |
| } |
| |
| Integer uniqueIdCount = (Integer) view.getAttributes().get(UNIQUE_ID_COUNTER_KEY); |
| if (uniqueIdCount != null && !uniqueIdCount.equals(1)) |
| { |
| serializedView = new Object[] { null, states, uniqueIdCount }; |
| } |
| else if (states == null) |
| { |
| serializedView = EMPTY_STATES; |
| } |
| else |
| { |
| serializedView = new Object[] { null, states }; |
| } |
| |
| //If view cache enabled store the view state into the pool |
| if (!saveViewFully && _viewPoolProcessor != null) |
| { |
| if (viewResetable) |
| { |
| _viewPoolProcessor.pushResetableView(context, view, (FaceletState) faceletViewState); |
| } |
| else |
| { |
| _viewPoolProcessor.pushPartialView(context, view, (FaceletState) faceletViewState, count); |
| } |
| } |
| |
| context.getAttributes().put(StateManagerImpl.SERIALIZED_VIEW_REQUEST_ATTR, serializedView); |
| } |
| |
| return serializedView; |
| } |
| |
| private void restoreViewRootOnlyFromMap(final FacesContext context, final Object viewState, final UIComponent view) |
| { |
| // Only viewState found, process it but skip tree |
| // traversal, saving some time. |
| try |
| { |
| //Restore view |
| view.pushComponentToEL(context, view); |
| if (viewState != null && !(viewState instanceof AttachedFullStateWrapper)) |
| { |
| try |
| { |
| view.restoreState(context, viewState); |
| } |
| catch(Exception e) |
| { |
| throw new IllegalStateException( |
| "Error restoring component: " + view.getClientId(context), e); |
| } |
| } |
| } |
| finally |
| { |
| view.popComponentFromEL(context); |
| } |
| } |
| |
| private void restoreStateFromMap(final FacesContext context, final Map<String,Object> states, |
| final UIComponent component) |
| { |
| if (states == null) |
| { |
| return; |
| } |
| |
| try |
| { |
| //Restore view |
| component.pushComponentToEL(context, component); |
| Object state = states.get(component.getClientId(context)); |
| if (state != null) |
| { |
| if (state instanceof AttachedFullStateWrapper) |
| { |
| //Don't restore this one! It will be restored when the algorithm remove and add it. |
| return; |
| } |
| try |
| { |
| component.restoreState(context, state); |
| } |
| catch(Exception e) |
| { |
| throw new IllegalStateException( |
| "Error restoring component: " + component.getClientId(context), e); |
| } |
| } |
| |
| //Scan children |
| if (component.getChildCount() > 0) |
| { |
| List<UIComponent> children = component.getChildren(); |
| for (int i = 0; i < children.size(); i++) |
| { |
| UIComponent child = children.get(i); |
| if (child != null && !child.isTransient()) |
| { |
| restoreStateFromMap(context, states, child); |
| } |
| } |
| } |
| |
| //Scan facets |
| if (component.getFacetCount() > 0) |
| { |
| Map<String, UIComponent> facetMap = component.getFacets(); |
| for (Map.Entry<String, UIComponent> entry : facetMap.entrySet()) |
| { |
| UIComponent child = entry.getValue(); |
| if (child != null && !child.isTransient()) |
| { |
| restoreStateFromMap(context, states, child); |
| } |
| } |
| } |
| } |
| finally |
| { |
| component.popComponentFromEL(context); |
| } |
| } |
| |
| static List<String> getClientIdsAdded(UIViewRoot root) |
| { |
| return (List<String>) root.getAttributes().get(CLIENTIDS_ADDED); |
| } |
| |
| static void setClientsIdsAdded(UIViewRoot root, List<String> clientIdsList) |
| { |
| root.getAttributes().put(CLIENTIDS_ADDED, clientIdsList); |
| } |
| |
| static List<String> getClientIdsRemoved(UIViewRoot root) |
| { |
| return (List<String>) root.getAttributes().get(CLIENTIDS_REMOVED); |
| } |
| |
| static void setClientsIdsRemoved(UIViewRoot root, List<String> clientIdsList) |
| { |
| root.getAttributes().put(CLIENTIDS_REMOVED, clientIdsList); |
| } |
| |
| @SuppressWarnings("unchecked") |
| private void registerOnAddRemoveList(FacesContext facesContext, String clientId) |
| { |
| UIViewRoot uiViewRoot = facesContext.getViewRoot(); |
| |
| List<String> clientIdsAdded = (List<String>) getClientIdsAdded(uiViewRoot); |
| if (clientIdsAdded == null) |
| { |
| //Create a set that preserve insertion order |
| clientIdsAdded = new ArrayList<>(); |
| } |
| clientIdsAdded.add(clientId); |
| |
| setClientsIdsAdded(uiViewRoot, clientIdsAdded); |
| |
| List<String> clientIdsRemoved = (List<String>) getClientIdsRemoved(uiViewRoot); |
| if (clientIdsRemoved == null) |
| { |
| //Create a set that preserve insertion order |
| clientIdsRemoved = new ArrayList<>(); |
| } |
| |
| clientIdsRemoved.add(clientId); |
| |
| setClientsIdsRemoved(uiViewRoot, clientIdsRemoved); |
| } |
| |
| @SuppressWarnings("unchecked") |
| private void registerOnAddList(FacesContext facesContext, String clientId) |
| { |
| UIViewRoot uiViewRoot = facesContext.getViewRoot(); |
| |
| List<String> clientIdsAdded = (List<String>) getClientIdsAdded(uiViewRoot); |
| if (clientIdsAdded == null) |
| { |
| //Create a set that preserve insertion order |
| clientIdsAdded = new ArrayList<>(); |
| } |
| clientIdsAdded.add(clientId); |
| |
| setClientsIdsAdded(uiViewRoot, clientIdsAdded); |
| } |
| |
| private void saveStateOnMapVisitTree(final FacesContext facesContext, final Map<String,Object> states, |
| final UIViewRoot uiViewRoot) |
| { |
| facesContext.getAttributes().put(MyFacesVisitHints.SKIP_ITERATION_HINT, Boolean.TRUE); |
| try |
| { |
| uiViewRoot.visitTree(getVisitContextFactory().getVisitContext(facesContext, null, |
| MyFacesVisitHints.SET_SKIP_ITERATION), new VisitCallback() |
| { |
| @Override |
| public VisitResult visit(VisitContext context, UIComponent target) |
| { |
| FacesContext facesContext = context.getFacesContext(); |
| Object state; |
| |
| if ((target == null) || target.isTransient()) |
| { |
| // No need to bother with these components or their children. |
| |
| return VisitResult.REJECT; |
| } |
| |
| ComponentState componentAddedAfterBuildView |
| = (ComponentState) target.getAttributes().get(COMPONENT_ADDED_AFTER_BUILD_VIEW); |
| |
| //Note if UIViewRoot has this marker, JSF 1.2 like state saving is used. |
| if (componentAddedAfterBuildView != null && (target.getParent() != null)) |
| { |
| if (ComponentState.REMOVE_ADD.equals(componentAddedAfterBuildView)) |
| { |
| registerOnAddRemoveList(facesContext, target.getClientId(facesContext)); |
| target.getAttributes().put(COMPONENT_ADDED_AFTER_BUILD_VIEW, ComponentState.ADDED); |
| } |
| else if (ComponentState.ADD.equals(componentAddedAfterBuildView)) |
| { |
| registerOnAddList(facesContext, target.getClientId(facesContext)); |
| target.getAttributes().put(COMPONENT_ADDED_AFTER_BUILD_VIEW, ComponentState.ADDED); |
| } |
| else if (ComponentState.ADDED.equals(componentAddedAfterBuildView)) |
| { |
| registerOnAddList(facesContext, target.getClientId(facesContext)); |
| } |
| ensureClearInitialState(target); |
| //Save all required info to restore the subtree. |
| //This includes position, structure and state of subtree |
| |
| int childIndex = target.getParent().getChildren().indexOf(target); |
| if (childIndex >= 0) |
| { |
| states.put(target.getClientId(facesContext), new AttachedFullStateWrapper( |
| new Object[]{ |
| target.getParent().getClientId(facesContext), |
| null, |
| childIndex, |
| internalBuildTreeStructureToSave(target), |
| target.processSaveState(facesContext)})); |
| } |
| else |
| { |
| String facetName = null; |
| if (target.getParent().getFacetCount() > 0) |
| { |
| for (Map.Entry<String, UIComponent> entry : target.getParent().getFacets().entrySet()) |
| { |
| if (target.equals(entry.getValue())) |
| { |
| facetName = entry.getKey(); |
| break; |
| } |
| } |
| } |
| states.put(target.getClientId(facesContext),new AttachedFullStateWrapper(new Object[]{ |
| target.getParent().getClientId(facesContext), |
| facetName, |
| null, |
| internalBuildTreeStructureToSave(target), |
| target.processSaveState(facesContext)})); |
| } |
| return VisitResult.REJECT; |
| } |
| else if (target.getParent() != null) |
| { |
| state = target.saveState (facesContext); |
| if (state != null) |
| { |
| // Save by client ID into our map. |
| states.put(target.getClientId(facesContext), state); |
| } |
| |
| return VisitResult.ACCEPT; |
| } |
| else |
| { |
| //Only UIViewRoot has no parent in a component tree. |
| return VisitResult.ACCEPT; |
| } |
| } |
| }); |
| } |
| finally |
| { |
| facesContext.getAttributes().remove(MyFacesVisitHints.SKIP_ITERATION_HINT); |
| } |
| if (!uiViewRoot.isTransient()) |
| { |
| Object state = uiViewRoot.saveState (facesContext); |
| if (state != null) |
| { |
| // Save by client ID into our map. |
| states.put(uiViewRoot.getClientId(facesContext), state); |
| } |
| } |
| } |
| |
| |
| private SaveStateAndResetViewCallback saveStateOnMapVisitTreeAndReset(final FacesContext facesContext, |
| final Map<String,Object> states, final UIViewRoot uiViewRoot, boolean forceHardReset) |
| { |
| facesContext.getAttributes().put(MyFacesVisitHints.SKIP_ITERATION_HINT, Boolean.TRUE); |
| SaveStateAndResetViewCallback callback = new SaveStateAndResetViewCallback( |
| facesContext.getViewRoot(), states, forceHardReset); |
| if (forceHardReset) |
| { |
| uiViewRoot.getAttributes().put(ViewPoolProcessor.RESET_SAVE_STATE_MODE_KEY, |
| ViewPoolProcessor.RESET_MODE_HARD); |
| } |
| else |
| { |
| uiViewRoot.getAttributes().put(ViewPoolProcessor.RESET_SAVE_STATE_MODE_KEY, |
| ViewPoolProcessor.RESET_MODE_SOFT); |
| } |
| try |
| { |
| if (_viewPoolProcessor != null && |
| !_viewPoolProcessor.isViewPoolEnabledForThisView(facesContext, uiViewRoot)) |
| { |
| callback.setViewResetable(false); |
| } |
| |
| // Check if the view has removed components. If that so, it |
| // means there is some manipulation over the component tree that |
| // can be rollback, so it is ok to set the view as resetable. |
| if (callback.isViewResetable()) |
| { |
| List<String> removedIds = getClientIdsRemoved(uiViewRoot); |
| if (removedIds != null && !removedIds.isEmpty()) |
| { |
| callback.setViewResetable(false); |
| } |
| } |
| |
| try |
| { |
| uiViewRoot.visitTree(getVisitContextFactory().getVisitContext( |
| facesContext, null, MyFacesVisitHints.SET_SKIP_ITERATION), callback); |
| } |
| finally |
| { |
| facesContext.getAttributes().remove(MyFacesVisitHints.SKIP_ITERATION_HINT); |
| } |
| |
| if (callback.isViewResetable() && callback.isRemoveAddedComponents()) |
| { |
| List<String> clientIdsToRemove = getClientIdsAdded(uiViewRoot); |
| |
| if (clientIdsToRemove != null) |
| { |
| RemoveComponentCallback removeCallback = null; |
| |
| // perf: clientIds are ArrayList: see method registerOnAddRemoveList(String) |
| for (int i = 0, size = clientIdsToRemove.size(); i < size; i++) |
| { |
| if (removeCallback == null) |
| { |
| removeCallback = new RemoveComponentCallback(); |
| } |
| removeCallback.setComponentFound(false); |
| |
| String clientId = clientIdsToRemove.get(i); |
| uiViewRoot.invokeOnComponent(facesContext, clientId, removeCallback); |
| } |
| } |
| } |
| |
| Object state = uiViewRoot.saveState(facesContext); |
| if (state != null) |
| { |
| // Save by client ID into our map. |
| states.put(uiViewRoot.getClientId (facesContext), state); |
| |
| //Hard reset (or reset and check state again) |
| Integer oldResetMode = (Integer) uiViewRoot.getAttributes().put( |
| ViewPoolProcessor.RESET_SAVE_STATE_MODE_KEY, ViewPoolProcessor.RESET_MODE_HARD); |
| state = uiViewRoot.saveState(facesContext); |
| uiViewRoot.getAttributes().put(ViewPoolProcessor.RESET_SAVE_STATE_MODE_KEY, oldResetMode); |
| if (state != null) |
| { |
| callback.setViewResetable(false); |
| } |
| } |
| } |
| finally |
| { |
| uiViewRoot.getAttributes().put(ViewPoolProcessor.RESET_SAVE_STATE_MODE_KEY, |
| ViewPoolProcessor.RESET_MODE_OFF); |
| } |
| return callback; |
| } |
| |
| private class SaveStateAndResetViewCallback implements VisitCallback |
| { |
| private final Map<String, Object> states; |
| private final UIViewRoot view; |
| private boolean viewResetable; |
| private boolean skipRoot; |
| private int count; |
| private boolean forceHardReset; |
| private boolean removeAddedComponents; |
| |
| public SaveStateAndResetViewCallback(UIViewRoot view, Map<String, Object> states, boolean forceHardReset) |
| { |
| this.states = states; |
| this.view = view; |
| this.viewResetable = true; |
| this.skipRoot = true; |
| this.count = 0; |
| this.forceHardReset = forceHardReset; |
| this.removeAddedComponents = false; |
| } |
| |
| @Override |
| public VisitResult visit(VisitContext context, UIComponent target) |
| { |
| FacesContext facesContext = context.getFacesContext(); |
| Object state; |
| this.count++; |
| |
| if ((target == null) || target.isTransient()) |
| { |
| // No need to bother with these components or their children. |
| return VisitResult.REJECT; |
| } |
| |
| if (skipRoot && target instanceof UIViewRoot) |
| { |
| //UIViewRoot should be scanned at last. |
| skipRoot = false; |
| return VisitResult.ACCEPT; |
| } |
| |
| ComponentState componentAddedAfterBuildView |
| = (ComponentState) target.getAttributes().get(COMPONENT_ADDED_AFTER_BUILD_VIEW); |
| |
| //Note if UIViewRoot has this marker, JSF 1.2 like state saving is used. |
| if (componentAddedAfterBuildView != null && (target.getParent() != null)) |
| { |
| //Set this view as not resetable. |
| //setViewResetable(false); |
| // Enable flag to remove added components later |
| setRemoveAddedComponents(true); |
| if (forceHardReset) |
| { |
| // The ideal is remove the added component here but visitTree does not support that |
| // kind of tree manipulation. |
| if (isViewResetable() && |
| ComponentState.REMOVE_ADD.equals(componentAddedAfterBuildView)) |
| { |
| setViewResetable(false); |
| } |
| // it is not important to save anything, skip |
| return VisitResult.REJECT; |
| } |
| if (ComponentState.REMOVE_ADD.equals(componentAddedAfterBuildView)) |
| { |
| //If the view has removed components, set the view as non resetable |
| setViewResetable(false); |
| registerOnAddRemoveList(facesContext, target.getClientId(facesContext)); |
| target.getAttributes().put(COMPONENT_ADDED_AFTER_BUILD_VIEW, ComponentState.ADDED); |
| } |
| else if (ComponentState.ADD.equals(componentAddedAfterBuildView)) |
| { |
| registerOnAddList(facesContext, target.getClientId(facesContext)); |
| target.getAttributes().put(COMPONENT_ADDED_AFTER_BUILD_VIEW, ComponentState.ADDED); |
| } |
| else if (ComponentState.ADDED.equals(componentAddedAfterBuildView)) |
| { |
| // Later on the check of removed components we'll see if the view |
| // is resetable or not. |
| registerOnAddList(facesContext, target.getClientId(facesContext)); |
| } |
| ensureClearInitialState(target); |
| //Save all required info to restore the subtree. |
| //This includes position, structure and state of subtree |
| |
| int childIndex = target.getParent().getChildren().indexOf(target); |
| if (childIndex >= 0) |
| { |
| states.put(target.getClientId(facesContext), new AttachedFullStateWrapper( |
| new Object[]{ |
| target.getParent().getClientId(facesContext), |
| null, |
| childIndex, |
| internalBuildTreeStructureToSave(target), |
| target.processSaveState(facesContext)})); |
| } |
| else |
| { |
| String facetName = null; |
| if (target.getParent().getFacetCount() > 0) |
| { |
| for (Map.Entry<String, UIComponent> entry : target.getParent().getFacets().entrySet()) |
| { |
| if (target.equals(entry.getValue())) |
| { |
| facetName = entry.getKey(); |
| break; |
| } |
| } |
| } |
| states.put(target.getClientId(facesContext), new AttachedFullStateWrapper(new Object[]{ |
| target.getParent().getClientId(facesContext), |
| facetName, |
| null, |
| internalBuildTreeStructureToSave(target), |
| target.processSaveState(facesContext)})); |
| } |
| return VisitResult.REJECT; |
| } |
| else if (target.getParent() != null) |
| { |
| if (forceHardReset) |
| { |
| // force hard reset set reset move on top |
| state = target.saveState (facesContext); |
| if (state != null) |
| { |
| setViewResetable(false); |
| return VisitResult.REJECT; |
| } |
| } |
| else |
| { |
| state = target.saveState (facesContext); |
| |
| if (state != null) |
| { |
| // Save by client ID into our map. |
| states.put(target.getClientId (facesContext), state); |
| |
| if (isViewResetable()) |
| { |
| //Hard reset (or reset and check state again) |
| Integer oldResetMode = (Integer) view.getAttributes().put( |
| ViewPoolProcessor.RESET_SAVE_STATE_MODE_KEY, |
| ViewPoolProcessor.RESET_MODE_HARD); |
| state = target.saveState (facesContext); |
| view.getAttributes().put(ViewPoolProcessor.RESET_SAVE_STATE_MODE_KEY, oldResetMode); |
| if (state != null) |
| { |
| setViewResetable(false); |
| } |
| } |
| } |
| } |
| |
| return VisitResult.ACCEPT; |
| } |
| else |
| { |
| //Only UIViewRoot has no parent in a component tree. |
| return VisitResult.ACCEPT; |
| } |
| } |
| |
| public boolean isViewResetable() |
| { |
| return viewResetable; |
| } |
| |
| public void setViewResetable(boolean viewResetable) |
| { |
| this.viewResetable = viewResetable; |
| } |
| |
| public int getCount() |
| { |
| return count; |
| } |
| |
| public boolean isRemoveAddedComponents() |
| { |
| return removeAddedComponents; |
| } |
| |
| public void setRemoveAddedComponents(boolean removeAddedComponents) |
| { |
| this.removeAddedComponents = removeAddedComponents; |
| } |
| } |
| |
| protected void ensureClearInitialState(UIComponent c) |
| { |
| c.clearInitialState(); |
| if (c.getChildCount() > 0) |
| { |
| for (int i = 0, childCount = c.getChildCount(); i < childCount; i++) |
| { |
| UIComponent child = c.getChildren().get(i); |
| ensureClearInitialState(child); |
| } |
| } |
| if (c.getFacetCount() > 0) |
| { |
| for (UIComponent child : c.getFacets().values()) |
| { |
| ensureClearInitialState(child); |
| } |
| } |
| } |
| |
| public void suscribeListeners(UIViewRoot uiViewRoot) |
| { |
| boolean listenerSubscribed = false; |
| List<SystemEventListener> pavList = uiViewRoot.getViewListenersForEventClass(PostAddToViewEvent.class); |
| if (pavList != null) |
| { |
| for (SystemEventListener listener : pavList) |
| { |
| if (listener instanceof PostAddPreRemoveFromViewListener) |
| { |
| listenerSubscribed = true; |
| break; |
| } |
| } |
| } |
| if (!listenerSubscribed) |
| { |
| PostAddPreRemoveFromViewListener componentListener = new PostAddPreRemoveFromViewListener(); |
| uiViewRoot.subscribeToViewEvent(PostAddToViewEvent.class, componentListener); |
| uiViewRoot.subscribeToViewEvent(PreRemoveFromViewEvent.class, componentListener); |
| } |
| } |
| |
| protected RenderKitFactory getRenderKitFactory() |
| { |
| if (_renderKitFactory == null) |
| { |
| _renderKitFactory = (RenderKitFactory)FactoryFinder.getFactory(FactoryFinder.RENDER_KIT_FACTORY); |
| } |
| return _renderKitFactory; |
| } |
| |
| protected VisitContextFactory getVisitContextFactory() |
| { |
| if (_visitContextFactory == null) |
| { |
| _visitContextFactory = (VisitContextFactory)FactoryFinder.getFactory(FactoryFinder.VISIT_CONTEXT_FACTORY); |
| } |
| return _visitContextFactory; |
| } |
| |
| |
| public static class PostAddPreRemoveFromViewListener implements SystemEventListener |
| { |
| private transient FacesContext _facesContext; |
| |
| private transient Boolean _isRefreshOnTransientBuildPreserveState; |
| |
| @Override |
| public boolean isListenerForSource(Object source) |
| { |
| // PostAddToViewEvent and PreRemoveFromViewEvent are |
| // called from UIComponentBase.setParent |
| return (source instanceof UIComponent); |
| } |
| |
| private boolean isRefreshOnTransientBuildPreserveState() |
| { |
| if (_isRefreshOnTransientBuildPreserveState == null) |
| { |
| _isRefreshOnTransientBuildPreserveState = MyfacesConfig.getCurrentInstance(_facesContext) |
| .isRefreshTransientBuildOnPSSPreserveState(); |
| } |
| return _isRefreshOnTransientBuildPreserveState; |
| } |
| |
| @Override |
| public void processEvent(SystemEvent event) |
| { |
| UIComponent component = (UIComponent) event.getSource(); |
| |
| if (component.isTransient()) |
| { |
| return; |
| } |
| |
| // This is a view listener. It is not saved on the state and this listener |
| // is suscribed each time the view is restored, so we can cache facesContext |
| // here |
| if (_facesContext == null) |
| { |
| _facesContext = FacesContext.getCurrentInstance(); |
| } |
| |
| if (event instanceof PostAddToViewEvent) |
| { |
| if (!isRefreshOnTransientBuildPreserveState() && |
| Boolean.TRUE.equals(_facesContext.getAttributes().get(StateManager.IS_BUILDING_INITIAL_STATE))) |
| { |
| return; |
| } |
| |
| //PostAddToViewEvent |
| component.getAttributes().put(COMPONENT_ADDED_AFTER_BUILD_VIEW, ComponentState.ADD); |
| } |
| else |
| { |
| //FacesContext facesContext = FacesContext.getCurrentInstance(); |
| // In this case if we are removing components on build, it is not necessary to register |
| // again the current id, and its more, it could cause a concurrent exception. But note |
| // we need to propagate PreRemoveFromViewEvent, otherwise the view will not be restored |
| // correctly. |
| if (FaceletViewDeclarationLanguage.isRemovingComponentBuild(_facesContext)) |
| { |
| return; |
| } |
| |
| if (!isRefreshOnTransientBuildPreserveState() && |
| FaceletCompositionContext.getCurrentInstance(_facesContext) != null && |
| (component.getAttributes().containsKey(ComponentSupport.MARK_CREATED) || |
| component.getAttributes().containsKey(ComponentSupport.COMPONENT_ADDED_BY_HANDLER_MARKER)) |
| ) |
| { |
| // Components removed by facelets algorithm does not need to be registered |
| // unless preserve state mode is used, because PSS initial state is changed |
| // to restore delta properly. |
| // MYFACES-3554 It is possible to find use cases where a component |
| // created by a facelet tag is changed dynamically in some way in render |
| // response time, so we need to check here also when facelets algorithm |
| // is running or not. |
| return; |
| } |
| |
| //PreRemoveFromViewEvent |
| UIViewRoot uiViewRoot = _facesContext.getViewRoot(); |
| |
| List<String> clientIdsRemoved = getClientIdsRemoved(uiViewRoot); |
| if (clientIdsRemoved == null) |
| { |
| //Create a set that preserve insertion order |
| clientIdsRemoved = new ArrayList<>(); |
| } |
| clientIdsRemoved.add(component.getClientId(_facesContext)); |
| setClientsIdsRemoved(uiViewRoot, clientIdsRemoved); |
| } |
| } |
| } |
| |
| private static TreeStructComponent internalBuildTreeStructureToSave(UIComponent component) |
| { |
| TreeStructComponent structComp = new TreeStructComponent(component.getClass().getName(), |
| component.getId()); |
| |
| //children |
| if (component.getChildCount() > 0) |
| { |
| List<TreeStructComponent> structChildList = new ArrayList<>(); |
| for (int i = 0, childCount = component.getChildCount(); i < childCount; i++) |
| { |
| UIComponent child = component.getChildren().get(i); |
| if (!child.isTransient()) |
| { |
| TreeStructComponent structChild = internalBuildTreeStructureToSave(child); |
| structChildList.add(structChild); |
| } |
| } |
| |
| TreeStructComponent[] childArray = structChildList.toArray(new TreeStructComponent[structChildList.size()]); |
| structComp.setChildren(childArray); |
| } |
| |
| //facets |
| if (component.getFacetCount() > 0) |
| { |
| Map<String, UIComponent> facetMap = component.getFacets(); |
| List<Object[]> structFacetList = new ArrayList<>(); |
| for (Map.Entry<String, UIComponent> entry : facetMap.entrySet()) |
| { |
| UIComponent child = entry.getValue(); |
| if (!child.isTransient()) |
| { |
| String facetName = entry.getKey(); |
| TreeStructComponent structChild = internalBuildTreeStructureToSave(child); |
| structFacetList.add(new Object[] {facetName, structChild}); |
| } |
| } |
| |
| Object[] facetArray = structFacetList.toArray(new Object[structFacetList.size()]); |
| structComp.setFacets(facetArray); |
| } |
| |
| return structComp; |
| } |
| |
| private static UIComponent internalRestoreTreeStructure(TreeStructComponent treeStructComp) |
| { |
| String compClass = treeStructComp.getComponentClass(); |
| String compId = treeStructComp.getComponentId(); |
| UIComponent component = (UIComponent)ClassUtils.newInstance(compClass); |
| component.setId(compId); |
| |
| //children |
| TreeStructComponent[] childArray = treeStructComp.getChildren(); |
| if (childArray != null) |
| { |
| List<UIComponent> childList = component.getChildren(); |
| for (int i = 0, len = childArray.length; i < len; i++) |
| { |
| UIComponent child = internalRestoreTreeStructure(childArray[i]); |
| childList.add(child); |
| } |
| } |
| |
| //facets |
| Object[] facetArray = treeStructComp.getFacets(); |
| if (facetArray != null) |
| { |
| Map<String, UIComponent> facetMap = component.getFacets(); |
| for (int i = 0, len = facetArray.length; i < len; i++) |
| { |
| Object[] tuple = (Object[])facetArray[i]; |
| String facetName = (String)tuple[0]; |
| TreeStructComponent structChild = (TreeStructComponent)tuple[1]; |
| UIComponent child = internalRestoreTreeStructure(structChild); |
| facetMap.put(facetName, child); |
| } |
| } |
| |
| return component; |
| } |
| |
| public static class TreeStructComponent implements Serializable |
| { |
| private static final long serialVersionUID = 5069109074684737231L; |
| private String _componentClass; |
| private String _componentId; |
| private TreeStructComponent[] _children = null; // Array of children |
| private Object[] _facets = null; // Array of Array-tuples with Facetname and TreeStructComponent |
| |
| TreeStructComponent(String componentClass, String componentId) |
| { |
| _componentClass = componentClass; |
| _componentId = componentId; |
| } |
| |
| public String getComponentClass() |
| { |
| return _componentClass; |
| } |
| |
| public String getComponentId() |
| { |
| return _componentId; |
| } |
| |
| void setChildren(TreeStructComponent[] children) |
| { |
| _children = children; |
| } |
| |
| TreeStructComponent[] getChildren() |
| { |
| return _children; |
| } |
| |
| Object[] getFacets() |
| { |
| return _facets; |
| } |
| |
| void setFacets(Object[] facets) |
| { |
| _facets = facets; |
| } |
| } |
| |
| } |