/* * The contents of this file are subject to the terms * of the Common Development and Distribution License * (the License). You may not use this file except in * compliance with the License. * * You can obtain a copy of the License at * https://javaserverfaces.dev.java.net/CDDL.html or * legal/CDDLv1.0.txt. * See the License for the specific language governing * permission and limitations under the License. * * When distributing Covered Code, include this CDDL * Header Notice in each file and include the License file * at legal/CDDLv1.0.txt. * If applicable, add the following below the CDDL Header, * with the fields enclosed by brackets [] replaced by * your own identifying information: * "Portions Copyrighted [year] [name of copyright owner]" * * [Name of File] [ver.__] [Date] * * Copyright 2006 Sun Microsystems Inc. All Rights Reserved */ package com.sun.faces.application; import javax.faces.FacesException; import javax.faces.application.StateManager; import javax.faces.component.NamingContainer; import javax.faces.component.UIComponent; import javax.faces.component.UIViewRoot; import javax.faces.context.ExternalContext; import javax.faces.context.FacesContext; import javax.faces.render.ResponseStateManager; import java.io.Externalizable; import java.io.IOException; import java.io.ObjectInput; import java.io.ObjectOutput; import java.util.ArrayList; 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.concurrent.ConcurrentHashMap; import java.util.logging.Level; import java.util.logging.Logger; import com.sun.faces.RIConstants; import com.sun.faces.config.WebConfiguration; import com.sun.faces.config.WebConfiguration.WebContextInitParameter; import com.sun.faces.renderkit.RenderKitUtils; import com.sun.faces.util.LRUMap; import com.sun.faces.util.MessageUtils; import com.sun.faces.util.Util; public class StateManagerImpl extends StateManager { private static final Logger LOGGER = Util.getLogger(Util.FACES_LOGGER + Util.APPLICATION_LOGGER); private HashMap responseStateManagerInfo = null; private char requestIdSerial = 0; /** Number of views in logical view to be saved in session. */ private int noOfViews = 0; private int noOfViewsInLogicalView = 0; private Map> classMap = new ConcurrentHashMap>(32); // ---------------------------------------------------------- Public Methods @SuppressWarnings("Deprecation") public UIViewRoot restoreView(FacesContext context, String viewId, String renderKitId) { UIViewRoot viewRoot = null; if (isSavingStateInClient(context)) { viewRoot = restoreTree(context, viewId, renderKitId); if (viewRoot != null) { restoreState(context, viewRoot, renderKitId); } } else { // restore tree from session. // The ResponseStateManager implementation may be using the new // methods or deprecated methods. We need to know which one // to call. Object id; ResponseStateManager rsm = RenderKitUtils.getResponseStateManager(context, renderKitId); if (hasDeclaredMethod(renderKitId, rsm, "getState")) { Object[] stateArray = (Object[]) rsm.getState(context, viewId); id = stateArray[0]; } else { id = rsm.getTreeStructureToRestore(context, viewId); } if (null != id) { if (LOGGER.isLoggable(Level.FINE)) { LOGGER.fine("Begin restoring view in session for viewId " + viewId); } String idString = (String) id; String idInLogicalMap; String idInActualMap; int sep = idString.indexOf(NamingContainer.SEPARATOR_CHAR); assert(-1 != sep); assert(sep < idString.length()); idInLogicalMap = idString.substring(0, sep); idInActualMap = idString.substring(sep + 1); ExternalContext externalCtx = context.getExternalContext(); Object sessionObj = externalCtx.getSession(false); // stop evaluating if the session is not available if (sessionObj == null) { if (LOGGER.isLoggable(Level.FINE)) { LOGGER.fine( "Can't Restore Server View State, session expired for viewId: " + viewId); } return null; } Object [] stateArray = null; synchronized (sessionObj) { Map logicalMap = (Map) externalCtx.getSessionMap() .get(RIConstants.LOGICAL_VIEW_MAP); if (logicalMap != null) { Map actualMap = (Map) logicalMap.get(idInLogicalMap); if (actualMap != null) { Map requestMap = context.getExternalContext().getRequestMap(); requestMap.put(RIConstants.LOGICAL_VIEW_MAP, idInLogicalMap); if (rsm.isPostback(context)) { requestMap.put(RIConstants.ACTUAL_VIEW_MAP, idInActualMap); } stateArray = (Object[]) actualMap.get(idInActualMap); } } } if (stateArray == null) { if (LOGGER.isLoggable(Level.FINE)) { LOGGER.fine( "Session Available, but View State does not exist for viewId: " + viewId); } return null; } viewRoot = restoreTree(((Object[]) stateArray[0])); viewRoot.processRestoreState(context, stateArray[1]); if (LOGGER.isLoggable(Level.FINE)) { LOGGER.fine("End restoring view in session for viewId " + viewId); } } } return viewRoot; } @SuppressWarnings("Deprecation") @Override public SerializedView saveSerializedView(FacesContext context) { SerializedView result = null; // irrespective of method to save the tree, if the root is transient // no state information needs to be persisted. UIViewRoot viewRoot = context.getViewRoot(); if (viewRoot.isTransient()) { return result; } // honor the requirement to check for id uniqueness checkIdUniqueness(context, viewRoot, new HashSet()); if (LOGGER.isLoggable(Level.FINE)) { LOGGER.fine("Begin creating serialized view for " + viewRoot.getViewId()); } List treeList = new ArrayList(32); captureChild(treeList, 0, viewRoot); Object state = viewRoot.processSaveState(context); Object[] tree = treeList.toArray(); if (LOGGER.isLoggable(Level.FINE)) { LOGGER.fine("End creating serialized view " + viewRoot.getViewId()); } if (!isSavingStateInClient(context)) { // // Server Side state saving is handled stored in two nested LRU maps // in the session. // // The first map is called the LOGICAL_VIEW_MAP. A logical view // is a top level view that may have one or more actual views inside // of it. This will be the case when you have a frameset, or an // application that has multiple windows operating at the same time. // The LOGICAL_VIEW_MAP map contains // an entry for each logical view, up to the limit specified by the // numberOfViewsParameter. Each entry in the LOGICAL_VIEW_MAP // is an LRU Map, configured with the numberOfViewsInLogicalView // parameter. // // The motivation for this is to allow better memory tuning for // apps that need this multi-window behavior. int logicalMapSize = getNumberOfViewsParameter(context); int actualMapSize = getNumberOfViewsInLogicalViewParameter(context); ExternalContext externalContext = context.getExternalContext(); Object sessionObj = externalContext.getSession(true); Map sessionMap = Util.getSessionMap(context); synchronized (sessionObj) { LRUMap> logicalMap = (LRUMap>) sessionMap .get(RIConstants.LOGICAL_VIEW_MAP); if (logicalMap == null) { logicalMap = new LRUMap>( logicalMapSize); sessionMap.put(RIConstants.LOGICAL_VIEW_MAP, logicalMap); } Map requestMap = externalContext.getRequestMap(); String idInLogicalMap = (String) requestMap.get(RIConstants.LOGICAL_VIEW_MAP); if (idInLogicalMap == null) { idInLogicalMap = createUniqueRequestId(); } assert(null != idInLogicalMap); // this value will not be null if this is a post // back String idInActualMap = (String) requestMap.get(RIConstants.ACTUAL_VIEW_MAP); if (idInActualMap == null) { idInActualMap = createUniqueRequestId(); } LRUMap actualMap = logicalMap.get(idInLogicalMap); if (actualMap == null) { actualMap = new LRUMap(actualMapSize); logicalMap.put(idInLogicalMap, actualMap); } String id = idInLogicalMap + NamingContainer.SEPARATOR_CHAR + idInActualMap; result = new SerializedView(id, null); Object[] stateArray = actualMap.get(idInActualMap); // reuse the array if possible if (stateArray != null) { stateArray[0] = tree; stateArray[1] = state; } else { actualMap.put(idInActualMap, new Object[] { tree, state }); } } } else { result = new SerializedView(tree, state); } return result; } @SuppressWarnings("Deprecation") @Override public void writeState(FacesContext context, SerializedView state) throws IOException { String renderKitId = context.getViewRoot().getRenderKitId(); ResponseStateManager rsm = RenderKitUtils.getResponseStateManager(context, renderKitId); if (hasDeclaredMethod(renderKitId, rsm, "getState")) { Object[] stateArray = new Object[2]; stateArray[0] = state.getStructure(); stateArray[1] = state.getState(); rsm.writeState(context, stateArray); } else { rsm.writeState(context, state); } } // ------------------------------------------------------- Protected Methods protected void checkIdUniqueness(FacesContext context, UIComponent component, Set componentIds) throws IllegalStateException { // deal with children/facets that are marked transient. for (Iterator kids = component.getFacetsAndChildren(); kids.hasNext();) { UIComponent kid = kids.next(); // check for id uniqueness String id = kid.getClientId(context); if (componentIds.add(id)) { checkIdUniqueness(context, kid, componentIds); } else { if (LOGGER.isLoggable(Level.SEVERE)) { LOGGER.log(Level.SEVERE, "jsf.duplicate_component_id_error", id); } throw new IllegalStateException( MessageUtils.getExceptionMessageString( MessageUtils.DUPLICATE_COMPONENT_ID_ERROR_ID, id)); } } } // --------------------------------------------------------- Private Methods /** * Returns the value of ServletContextInitParameter that specifies the * maximum number of views to be saved in this logical view. If none is specified * returns DEFAULT_NUMBER_OF_VIEWS_IN_LOGICAL_VIEW_IN_SESSION. * @param context the FacesContext * @return number of logical views */ protected int getNumberOfViewsInLogicalViewParameter(FacesContext context) { if (noOfViewsInLogicalView != 0) { return noOfViewsInLogicalView; } WebConfiguration webConfig = WebConfiguration.getInstance(context.getExternalContext()); String noOfViewsStr = webConfig .getContextInitParameter(WebContextInitParameter.NumberOfLogicalViews); String defaultValue = WebContextInitParameter.NumberOfLogicalViews.getDefaultValue(); try { noOfViewsInLogicalView = Integer.valueOf(noOfViewsStr); } catch (NumberFormatException nfe) { if (LOGGER.isLoggable(Level.FINE)) { LOGGER.fine("Error parsing the servetInitParameter " + WebContextInitParameter.NumberOfLogicalViews.getQualifiedName() + ". Using default " + noOfViewsInLogicalView); } try { noOfViewsInLogicalView = Integer.valueOf(defaultValue); } catch (NumberFormatException ne) { // won't occur } } return noOfViewsInLogicalView; } /** * Returns the value of ServletContextInitParameter that specifies the * maximum number of logical views to be saved in session. If none is specified * returns DEFAULT_NUMBER_OF_VIEWS_IN_SESSION. * @param context the FacesContext * @return number of logical views */ protected int getNumberOfViewsParameter(FacesContext context) { if (noOfViews != 0) { return noOfViews; } WebConfiguration webConfig = WebConfiguration.getInstance(context.getExternalContext()); String noOfViewsStr = webConfig .getContextInitParameter(WebContextInitParameter.NumberOfViews); String defaultValue = WebContextInitParameter.NumberOfViews.getDefaultValue(); try { noOfViews = Integer.valueOf(noOfViewsStr); } catch (NumberFormatException nfe) { if (LOGGER.isLoggable(Level.FINE)) { LOGGER.fine("Error parsing the servetInitParameter " + WebContextInitParameter.NumberOfViews.getQualifiedName() + ". Using default " + noOfViews); } try { noOfViews = Integer.valueOf(defaultValue); } catch (NumberFormatException ne) { // won't occur } } return noOfViews; } private static void captureChild(List tree, int parent, UIComponent c) { if (!c.isTransient()) { TreeNode n = new TreeNode(parent, c); int pos = tree.size(); tree.add(n); captureRest(tree, pos, c); } } private static void captureFacet(List tree, int parent, String name, UIComponent c) { if (!c.isTransient()) { FacetNode n = new FacetNode(parent, name, c); int pos = tree.size(); tree.add(n); captureRest(tree, pos, c); } } private static void captureRest(List tree, int pos, UIComponent c) { // store children int sz = c.getChildCount(); if (sz > 0) { List child = c.getChildren(); for (int i = 0; i < sz; i++) { captureChild(tree, pos, child.get(i)); } } // store facets sz = c.getFacetCount(); if (sz > 0) { for (Entry entry : c.getFacets().entrySet()) { captureFacet(tree, pos, entry.getKey(), entry.getValue()); } } } /** * Looks for the presence of a declared method (by name) in the specified * class and returns a boolean outcome (true, if the method * exists). * * @param renderKitId The object that will be used for the lookup. This key * will also be stored in the Map with a corresponding * Boolean value indicating the result of the search. * @param instance The instance of the class that will be used as * the search domain. * @param methodName The name of the method we are looking for. * * @return true if the method exists, otherwise * false */ private boolean hasDeclaredMethod(String renderKitId, ResponseStateManager instance, String methodName) { boolean result; if (responseStateManagerInfo == null) { responseStateManagerInfo = new HashMap(); } Boolean value = responseStateManagerInfo.get(renderKitId); if (value != null) { return value; } result = Util.hasDeclaredMethod(instance, methodName); responseStateManagerInfo.put(renderKitId, result); return result; } private UIComponent newInstance(TreeNode n) throws FacesException { try { Class t = classMap.get(n.componentType); if (t == null) { t = Util.loadClass(n.componentType, n); if (t != null) { classMap.put(n.componentType, t); } else { throw new NullPointerException(); } } UIComponent c = (UIComponent) t.newInstance(); c.setId(n.id); return c; } catch (Exception e) { throw new FacesException(e); } } @SuppressWarnings("Deprecation") private void restoreState(FacesContext context, UIViewRoot root, String renderKitId) { ResponseStateManager rsm = RenderKitUtils.getResponseStateManager(context, renderKitId); Object state; if (hasDeclaredMethod(renderKitId, rsm, "getState")) { Object[] stateArray = (Object[]) rsm.getState(context, root.getViewId()); state = stateArray[1]; } else { state = rsm.getComponentStateToRestore(context); } root.processRestoreState(context, state); } @SuppressWarnings("Deprecation") private UIViewRoot restoreTree(FacesContext context, String viewId, String renderKitId) { ResponseStateManager rsm = RenderKitUtils.getResponseStateManager(context, renderKitId); Object[] treeStructure; if (hasDeclaredMethod(renderKitId, rsm, "getState")) { Object[] stateArray = (Object[]) rsm.getState(context, viewId); treeStructure = (Object[]) stateArray[0]; } else { treeStructure = (Object[]) rsm .getTreeStructureToRestore(context, viewId); } if (treeStructure == null) { return null; } return restoreTree(treeStructure); } private String createUniqueRequestId() { if (requestIdSerial++ == Character.MAX_VALUE) { requestIdSerial = 0; } return UIViewRoot.UNIQUE_ID_PREFIX + ((int) requestIdSerial); } private UIViewRoot restoreTree(Object[] tree) throws FacesException { UIComponent c; FacetNode fn; TreeNode tn; for (int i = 0; i < tree.length; i++) { if (tree[i]instanceof FacetNode) { fn = (FacetNode) tree[i]; c = newInstance(fn); tree[i] = c; if (i != fn.parent) { ((UIComponent) tree[fn.parent]).getFacets() .put(fn.facetName, c); } } else { tn = (TreeNode) tree[i]; c = newInstance(tn); tree[i] = c; if (i != tn.parent) { ((UIComponent) tree[tn.parent]).getChildren().add(c); } } } return (UIViewRoot) tree[0]; } private static class TreeNode implements Externalizable { public String componentType; public String id; public int parent; private static final long serialVersionUID = -835775352718473281L; // ------------------------------------------------------------ Constructors public TreeNode() { } public TreeNode(int parent, UIComponent c) { this.parent = parent; this.id = c.getId(); this.componentType = c.getClass().getName(); } // --------------------------------------------- Methods From Externalizable public void writeExternal(ObjectOutput out) throws IOException { out.writeInt(this.parent); out.writeUTF(this.componentType); if (this.id != null) { out.writeUTF(this.id); } } public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { this.parent = in.readInt(); this.componentType = in.readUTF(); if (in.available() > 0) { this.id = in.readUTF(); } } } private static final class FacetNode extends TreeNode { public String facetName; private static final long serialVersionUID = -3777170310958005106L; // ------------------------------------------------------------ Constructors public FacetNode() { } public FacetNode(int parent, String name, UIComponent c) { super(parent, c); this.facetName = name; } // ---------------------------------------------------------- Public Methods @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { super.readExternal(in); this.facetName = in.readUTF(); } @Override public void writeExternal(ObjectOutput out) throws IOException { super.writeExternal(out); out.writeUTF(this.facetName); } } } // END StateManagerImpl