com.sun.faces.context.StateContext Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jakarta.faces Show documentation
Show all versions of jakarta.faces Show documentation
EE4J Compatible Implementation for Jakarta Faces API
The 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.faces.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.
* @param ctx the involved faces context
* @param root the involved view root
*/
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
* startTrackViewModifications
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).
* @return the dynamic list
*/
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