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

com.sun.faces.context.StateContext Maven / Gradle / Ivy

Go to download

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

There is a newer version: 4.1.0
Show newest version
/*
 * Copyright (c) 2012, 2020 Oracle and/or its affiliates. All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0, which is available at
 * http://www.eclipse.org/legal/epl-2.0.
 *
 * This Source Code may also be made available under the following Secondary
 * Licenses when the conditions for such availability set forth in the
 * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
 * version 2 with the GNU Classpath Exception, which is available at
 * https://www.gnu.org/software/classpath/license.html.
 *
 * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
 */

package com.sun.faces.context;

import static com.sun.faces.RIConstants.DYNAMIC_CHILD_COUNT;
import static com.sun.faces.RIConstants.DYNAMIC_COMPONENT;
import static com.sun.faces.util.ComponentStruct.ADD;
import static com.sun.faces.util.ComponentStruct.REMOVE;

import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.logging.Logger;

import com.sun.faces.RIConstants;
import com.sun.faces.application.ApplicationAssociate;
import com.sun.faces.application.ApplicationStateInfo;
import com.sun.faces.facelets.tag.jsf.ComponentSupport;
import com.sun.faces.util.ComponentStruct;
import com.sun.faces.util.FacesLogger;
import com.sun.faces.util.MostlySingletonSet;

import jakarta.faces.FacesException;
import jakarta.faces.component.UIComponent;
import jakarta.faces.component.UIViewRoot;
import jakarta.faces.context.FacesContext;
import jakarta.faces.event.AbortProcessingException;
import jakarta.faces.event.PostAddToViewEvent;
import jakarta.faces.event.PreRemoveFromViewEvent;
import jakarta.faces.event.SystemEvent;
import jakarta.faces.event.SystemEventListener;

/**
 * Context for dealing with partial state saving mechanics.
 */
public class StateContext {

    private static final String KEY = StateContext.class.getName() + "_KEY";

    private boolean partial;
    private boolean partialLocked;
    private boolean trackMods = true;
    private AddRemoveListener modListener;
    private ApplicationStateInfo stateInfo;
    private WeakReference viewRootRef = new WeakReference<>(null);

    private static final Logger LOGGER = FacesLogger.CONTEXT.getLogger();

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

    private StateContext(ApplicationStateInfo stateInfo) {

        this.stateInfo = stateInfo;

    }

    // ---------------------------------------------------------- Public Methods

    /**
     * Release the state context.
     *
     * @param facesContext the Faces context.
     */
    public static void release(FacesContext facesContext) {
        StateContext stateContext = (StateContext) facesContext.getAttributes().get(KEY);
        UIViewRoot viewRoot = facesContext.getViewRoot();
        if (viewRoot != null && stateContext.modListener != null) {
            viewRoot.unsubscribeFromViewEvent(PostAddToViewEvent.class, stateContext.modListener);
            viewRoot.unsubscribeFromViewEvent(PreRemoveFromViewEvent.class, stateContext.modListener);
        }
        facesContext.getAttributes().remove(KEY);
    }

    /**
     * @param ctx the FacesContext for the current request
     * @return StateContext for this request
     */
    public static StateContext getStateContext(FacesContext ctx) {

        StateContext stateCtx = (StateContext) ctx.getAttributes().get(KEY);
        if (stateCtx == null) {
            ApplicationAssociate associate = ApplicationAssociate.getCurrentInstance();
            ApplicationStateInfo info = associate.getApplicationStateInfo();
            stateCtx = new StateContext(info);
            ctx.getAttributes().put(KEY, stateCtx);
        }
        return stateCtx;

    }

    /**
     * @param ctx FacesContext.
     * @param viewId the view ID to check or null if viewId is unknown.
     * @return true if partial state saving should be used for the specified view ID, otherwise
     * false
     */
    public boolean isPartialStateSaving(FacesContext ctx, String viewId) {
        // track UIViewRoot changes
        UIViewRoot root = ctx.getViewRoot();
        UIViewRoot refRoot = viewRootRef.get();
        if (root != refRoot) {
            // set weak reference to current viewRoot
            viewRootRef = new WeakReference<>(root);

            // On first call in restore phase, viewRoot is null, so we treat the first
            // change to not null not as a changing viewRoot.
            if (refRoot != null) {
                // view root changed in request processing - force usage of a
                // new AddRemoveListener instance for the new viewId ...
                modListener = null;
                // ... and also force check for partial state saving for the new viewId
                partialLocked = false;
            }
        }

        if (!partialLocked) {
            if (viewId == null) {
                if (root != null) {
                    viewId = root.getViewId();
                } else {
                    // View root has not yet been initialized. Check to see whether
                    // the target view id has been stashed away for us.
                    viewId = (String) ctx.getAttributes().get(RIConstants.VIEWID_KEY_NAME);
                }
            }

            partial = stateInfo.usePartialStateSaving(viewId);
            partialLocked = true;
        }
        return partial;

    }

    /**
     * @return true if view modifications outside of the initial construction of the view are being tracked.
     */
    public boolean trackViewModifications() {

        return trackMods;

    }

    /**
     * Installs a SystemEventListener on the UIViewRoot to track components added to or removed
     * from the view.
     */
    public void startTrackViewModifications(FacesContext ctx, UIViewRoot root) {
        if (modListener == null) {
            if (root != null) {
                modListener = createAddRemoveListener(ctx, root);
                root.subscribeToViewEvent(PostAddToViewEvent.class, modListener);
                root.subscribeToViewEvent(PreRemoveFromViewEvent.class, modListener);
            } else {
                LOGGER.warning("Unable to attach AddRemoveListener to UIViewRoot because it is null");
            }
        }
        setTrackViewModifications(true);
    }

    /**
     * Toggles the current modification tracking status.
     *
     * @param trackMods if true and the listener installed by
     * {@link #startTrackViewModifications(jakarta.faces.context.FacesContext, jakarta.faces.component.UIViewRoot) is
     * present, then view modifications will be tracked. If false, then modification events will be ignored.
     */
    public void setTrackViewModifications(boolean trackMods) {

        this.trackMods = trackMods;

    }

    /**
     * @param c the UIComponent to check
     * @return true if the component was added after the initial view construction
     */
    public boolean componentAddedDynamically(UIComponent c) {
        return c.getAttributes().containsKey(DYNAMIC_COMPONENT);
    }

    public int getIndexOfDynamicallyAddedChildInParent(UIComponent c) {
        int result = -1;
        Map attrs = c.getAttributes();
        if (attrs.containsKey(DYNAMIC_COMPONENT)) {
            result = (Integer) attrs.get(DYNAMIC_COMPONENT);
        }
        return result;
    }

    public boolean hasOneOrMoreDynamicChild(UIComponent parent) {
        return parent.getAttributes().containsKey(DYNAMIC_CHILD_COUNT);
    }

    private int incrementDynamicChildCount(FacesContext context, UIComponent parent) {
        int result;
        Map attrs = parent.getAttributes();
        Integer cur = (Integer) attrs.get(DYNAMIC_CHILD_COUNT);
        if (null != cur) {
            result = cur++;
        } else {
            result = 1;
        }
        attrs.put(DYNAMIC_CHILD_COUNT, result);
        context.getViewRoot().getAttributes().put(RIConstants.TREE_HAS_DYNAMIC_COMPONENTS, Boolean.TRUE);

        return result;
    }

    private int decrementDynamicChildCount(FacesContext context, UIComponent parent) {
        int result = 0;
        Map attrs = parent.getAttributes();
        Integer cur = (Integer) attrs.get(DYNAMIC_CHILD_COUNT);
        if (null != cur) {
            result = 0 < cur ? cur-- : 0;

        }
        if (0 == result && null != cur) {
            attrs.remove(DYNAMIC_CHILD_COUNT);
        }
        context.getViewRoot().getAttributes().put(RIConstants.TREE_HAS_DYNAMIC_COMPONENTS, Boolean.TRUE);

        return result;
    }

    /**
     * Get the dynamic list (of adds and removes).
     */
    public List getDynamicActions() {
        return modListener != null ? modListener.getDynamicActions() : null;
    }

    /**
     * Get the hash map of dynamic components.
     *
     * @return the hash map of dynamic components.
     */
    public HashMap getDynamicComponents() {
        return modListener != null ? modListener.getDynamicComponents() : null;
    }

    // ---------------------------------------------------------- Nested Classes

    private AddRemoveListener createAddRemoveListener(FacesContext context, UIViewRoot root) {
        return isPartialStateSaving(context, root.getViewId()) ? new DynamicAddRemoveListener(context) : new StatelessAddRemoveListener(context);
    }

    abstract private class AddRemoveListener implements SystemEventListener {

        /**
         * Stores the state context we work for,
         */
        private StateContext stateCtx;

        /**
         * Constructor.
         *
         * @param context the Faces context.
         */
        protected AddRemoveListener(FacesContext context) {
            stateCtx = StateContext.getStateContext(context);
        }

        /**
         * Get the list of adds/removes.
         *
         * @return the list of adds/removes.
         */
        abstract public List getDynamicActions();

        /**
         * Get the hash map of dynamic components.
         *
         * @return the hash map of dynamic components.
         */
        abstract public HashMap getDynamicComponents();

        /**
         * Process the add/remove event.
         *
         * @param event the add/remove event.
         * @throws AbortProcessingException when processing should be aborted.
         */
        @Override
        public void processEvent(SystemEvent event) throws AbortProcessingException {
            FacesContext ctx = FacesContext.getCurrentInstance();
            if (event instanceof PreRemoveFromViewEvent) {
                if (stateCtx.trackViewModifications()) {
                    handleRemove(ctx, ((PreRemoveFromViewEvent) event).getComponent());
                    ctx.getViewRoot().getAttributes().put(RIConstants.TREE_HAS_DYNAMIC_COMPONENTS, Boolean.TRUE);
                }
            } else {
                if (stateCtx.trackViewModifications()) {
                    handleAdd(ctx, ((PostAddToViewEvent) event).getComponent());
                    ctx.getViewRoot().getAttributes().put(RIConstants.TREE_HAS_DYNAMIC_COMPONENTS, Boolean.TRUE);
                }
            }
        }

        /**
         * Are we listening for these particular changes.
         *
         * 

* Note we are only interested in UIComponent adds/removes that are not the UIViewRoot itself. *

* * @param source the source object we might be listening for. * @return true if the source is OK, false otherwise. */ @Override public boolean isListenerForSource(Object source) { return source instanceof UIComponent && !(source instanceof UIViewRoot); } /** * Handle the remove. * * @param context the Faces context. * @param component the UI component to add to the list as a REMOVE. */ abstract protected void handleRemove(FacesContext context, UIComponent component); /** * Handle the add. * * @param context the Faces context. * @param component the UI component to add to the list as an ADD. */ abstract protected void handleAdd(FacesContext context, UIComponent component); } public class NoopAddRemoveListener extends AddRemoveListener { // This is silly. We should be able to use Colletions.emptyMap(), // but cannot as StateContext.getDynamicComponents() API returns a // HashMap instead of a Map. private HashMap emptyComponentsMap = new HashMap(); public NoopAddRemoveListener(FacesContext context) { super(context); } @Override public List getDynamicActions() { return Collections.emptyList(); } @Override public HashMap getDynamicComponents() { return emptyComponentsMap; } @Override protected void handleRemove(FacesContext context, UIComponent component) { } @Override protected void handleAdd(FacesContext context, UIComponent component) { } } /** * An AddRemoveListener that implements the new dynamic component strategy where no state is managed by the listener * itself. Instead, we use expando attributes on the dynamic components (and their parents) to track/preserve the * dynamic nature of these components. */ public class StatelessAddRemoveListener extends NoopAddRemoveListener { public StatelessAddRemoveListener(FacesContext context) { super(context); } private boolean thisEventCorrespondsToSubtreeRootRemove(FacesContext context, UIComponent c) { boolean result = false; if (null != c) { c = c.getParent(); if (null != c) { result = c.isInView(); } } return result; } private boolean thisEventCorrespondsToSubtreeRootAdd(FacesContext context, UIComponent c) { boolean result = false; Map contextMap = context.getAttributes(); UIViewRoot root = context.getViewRoot(); UIComponent originalComponent = c; if (null != c) { Collection dynamics = getDynamicComponentCollection(contextMap); if (dynamics.contains(c)) { result = true; } else { c = c.getParent(); while (null != c && !dynamics.contains(c)) { c = c.getParent(); } if (null == c || root.equals(c)) { dynamics.add(originalComponent); result = true; } } } return result; } private static final String DYNAMIC_COMPONENT_ADD_COLLECTION = RIConstants.FACES_PREFIX + "DynamicComponentSubtreeRoots"; private Collection getDynamicComponentCollection(Map contextMap) { Collection result = (Collection) contextMap.get(DYNAMIC_COMPONENT_ADD_COLLECTION); if (null == result) { result = new HashSet<>(); contextMap.put(DYNAMIC_COMPONENT_ADD_COLLECTION, result); } return result; } @Override protected void handleRemove(FacesContext context, UIComponent component) { if (!thisEventCorrespondsToSubtreeRootRemove(context, component)) { return; } Map attrs = component.getAttributes(); // If the component is a tag-created child, we remove its // MARK_CREATED expando so that it will now be treated as // a dynamic/non-tag created component. String tagId = (String) attrs.remove(ComponentSupport.MARK_CREATED); if (tagId != null) { // Actually, we don't just remove the MARK_CREATED - we need // to stash it away so that we can restore it later if the // component happens to be re-added to its original parent. attrs.put(ComponentSupport.MARK_CREATED_REMOVED, tagId); childRemovedFromParent(component.getParent(), tagId); } } private void childRemovedFromParent(UIComponent parent, String childTagId) { if (parent != null) { Collection removedChildrenIds = getPreviouslyRemovedChildren(parent); removedChildrenIds.add(childTagId); markChildrenModified(parent); } } private Collection getPreviouslyRemovedChildren(UIComponent parent) { Map attrs = parent.getAttributes(); Collection removedChildrenIds = (Collection) attrs.get(ComponentSupport.REMOVED_CHILDREN); if (removedChildrenIds == null) { removedChildrenIds = new MostlySingletonSet<>(); attrs.put(ComponentSupport.REMOVED_CHILDREN, removedChildrenIds); } return removedChildrenIds; } private void markChildrenModified(UIComponent parent) { parent.getAttributes().put(ComponentSupport.MARK_CHILDREN_MODIFIED, true); } @Override protected void handleAdd(FacesContext context, UIComponent component) { if (!thisEventCorrespondsToSubtreeRootAdd(context, component)) { return; } Map attrs = component.getAttributes(); String tagId = (String) attrs.get(ComponentSupport.MARK_CREATED_REMOVED); if (childAddedToSameParentAsBefore(component.getParent(), tagId)) { // Restore MARK_CREATED if the added component was originally // created as a tag-based child of this parent. attrs.remove(ComponentSupport.MARK_CREATED_REMOVED); attrs.put(ComponentSupport.MARK_CREATED, tagId); } markChildrenModified(component.getParent()); } // Handles the addition of a new child to the parent. Returns true // if the child was previously removed from this parent. private boolean childAddedToSameParentAsBefore(UIComponent parent, String childTagId) { if (parent != null) { Map attrs = parent.getAttributes(); Collection removedChildrenIds = (Collection) attrs.get(ComponentSupport.REMOVED_CHILDREN); if (removedChildrenIds != null && removedChildrenIds.remove(childTagId)) { if (removedChildrenIds.isEmpty()) { attrs.remove(ComponentSupport.REMOVED_CHILDREN); } return true; } } return false; } } /** * A system event listener which is used to listen for changes on the component tree after restore view and before * rendering out the view. */ public class DynamicAddRemoveListener extends AddRemoveListener { /** * Stores the list of adds/removes. */ private List dynamicActions; /** * Stores the hash map of dynamic components. */ private transient HashMap dynamicComponents; /** * Constructor. * * @param context the Faces context. */ public DynamicAddRemoveListener(FacesContext context) { super(context); } /** * Get the list of adds/removes. * * @return the list of adds/removes. */ @Override public List getDynamicActions() { synchronized (this) { if (dynamicActions == null) { dynamicActions = new ArrayList<>(); } } return dynamicActions; } /** * Get the hash map of dynamic components. * * @return the hash map of dynamic components. */ @Override public HashMap getDynamicComponents() { synchronized (this) { if (dynamicComponents == null) { dynamicComponents = new HashMap<>(); } } return dynamicComponents; } /** * Handle the remove. * * @param context the Faces context. * @param component the UI component to add to the list as a REMOVE. */ @Override protected void handleRemove(FacesContext context, UIComponent component) { if (component.isInView()) { decrementDynamicChildCount(context, component.getParent()); handleAddRemoveWithAutoPrune(component, new ComponentStruct(REMOVE, component.getClientId(context), component.getId())); } } /** * Handle the add. * * @param context the Faces context. * @param component the UI component to add to the list as an ADD. */ @Override protected void handleAdd(FacesContext context, UIComponent component) { if (component.getParent() != null && component.getParent().isInView()) { String id = component.getId(); /* * Since adding a component, can mean you are really reparenting it, we need to make sure the OLD clientId is not * cached, we do that by setting the id. */ if (id != null) { component.setId(id); } String facetName = findFacetNameForComponent(component); if (facetName != null) { incrementDynamicChildCount(context, component.getParent()); component.clearInitialState(); component.getAttributes().put(DYNAMIC_COMPONENT, component.getParent().getChildren().indexOf(component)); ComponentStruct struct = new ComponentStruct(ADD, facetName, component.getParent().getClientId(context), component.getClientId(context), component.getId()); handleAddRemoveWithAutoPrune(component, struct); } else { incrementDynamicChildCount(context, component.getParent()); component.clearInitialState(); component.getAttributes().put(DYNAMIC_COMPONENT, component.getParent().getChildren().indexOf(component)); ComponentStruct struct = new ComponentStruct(ADD, null, component.getParent().getClientId(context), component.getClientId(context), component.getId()); handleAddRemoveWithAutoPrune(component, struct); } } } /** * Return the facet name for the given component or null if the component is not the value of a facets map entry. * * @param component the component to look for in the facets map entry value. * @return the facet name or null if the component is not the value of a facets map entry. */ private String findFacetNameForComponent(UIComponent component) { Set> entrySet = component.getParent().getFacets().entrySet(); Iterator> entries = entrySet.iterator(); while (entries.hasNext()) { Entry candidate = entries.next(); if (component == candidate.getValue()) { return candidate.getKey(); } } return null; } /** * Methods that takes care of pruning and adding an action to the dynamic action list. * *
         *  If you add a component and the dynamic action list does not contain
         *  the component yet then add it to the dynamic action list, regardless
         *  whether or not if was an ADD or REMOVE.
         * 
* *
         *  Else if you add a component and it is already in the dynamic action
         *  list and it is the only action for that client id in the dynamic
         *  action list then:
         *   1) If the previous action was an ADD then
         *      a) If the current action is a REMOVE then remove the component
         *         out of the dynamic action list.
         *      b) If the current action is an ADD then throw a FacesException.
         *   2) If the previous action was a REMOVE then
         *      a) If the current action is an ADD then add it to the dynamic
         *         action list.
         *      b) If the current action is a REMOVE then throw a FacesException.
         * 
* *
         *  Else if a REMOVE and ADD where captured before then:
         *   1) If the current action is REMOVE then remove the last dynamic
         *      action out of the dynamic action list.
         *   2) If the current action is ADD then throw a FacesException.
         * 
* * @param component the UI component. * @param struct the dynamic action. */ private void handleAddRemoveWithAutoPrune(UIComponent component, ComponentStruct struct) { List actionList = getDynamicActions(); HashMap componentMap = getDynamicComponents(); int firstIndex = actionList.indexOf(struct); if (firstIndex == -1) { actionList.add(struct); componentMap.put(struct.getClientId(), component); } else { int lastIndex = actionList.lastIndexOf(struct); if (lastIndex == firstIndex) { ComponentStruct previousStruct = actionList.get(firstIndex); if (ADD.equals(previousStruct.getAction())) { if (ADD.equals(struct.getAction())) { throw new FacesException("Cannot add the same component twice: " + struct.getClientId()); } if (REMOVE.equals(struct.getAction())) { actionList.remove(firstIndex); componentMap.remove(struct.getClientId()); } } if (REMOVE.equals(previousStruct.getAction())) { if (ADD.equals(struct.getAction())) { actionList.add(struct); componentMap.put(struct.getClientId(), component); } if (REMOVE.equals(struct.getAction())) { throw new FacesException("Cannot remove the same component twice: " + struct.getClientId()); } } } else { if (ADD.equals(struct.getAction())) { throw new FacesException("Cannot add the same component twice: " + struct.getClientId()); } if (REMOVE.equals(struct.getAction())) { actionList.remove(lastIndex); } } } } } // END AddRemoveListener }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy