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

com.sun.faces.renderkit.ServerSideStateHelper Maven / Gradle / Ivy

Go to download

This is the master POM file for Oracle's Implementation of the JSF 2.2 Specification.

The newest version!
/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 1997-2014 Oracle and/or its affiliates. All rights reserved.
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License.  You can
 * obtain a copy of the License at
 * https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
 * or packager/legal/LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at packager/legal/LICENSE.txt.
 *
 * GPL Classpath Exception:
 * Oracle designates this particular file as subject to the "Classpath"
 * exception as provided by Oracle in the GPL Version 2 section of the License
 * file that accompanied this code.
 *
 * Modifications:
 * If applicable, add the following below the License Header, with the fields
 * enclosed by brackets [] replaced by your own identifying information:
 * "Portions Copyright [year] [name of copyright owner]"
 *
 * Contributor(s):
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */

package com.sun.faces.renderkit;

import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.zip.GZIPOutputStream;
import java.util.zip.GZIPInputStream;
import java.util.Map;
import java.security.SecureRandom;
import java.util.concurrent.atomic.AtomicInteger;
import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.io.IOException;
import java.io.ByteArrayInputStream;
import java.io.ObjectInputStream;

import javax.faces.context.FacesContext;
import javax.faces.context.ExternalContext;
import javax.faces.context.ResponseWriter;
import javax.faces.FacesException;
import javax.faces.component.NamingContainer;
import javax.faces.component.UIViewRoot;

import com.sun.faces.config.WebConfiguration.WebContextInitParameter;
import static com.sun.faces.config.WebConfiguration.BooleanWebContextInitParameter.SerializeServerStateDeprecated;
import static com.sun.faces.config.WebConfiguration.BooleanWebContextInitParameter.SerializeServerState;
import static com.sun.faces.config.WebConfiguration.BooleanWebContextInitParameter.GenerateUniqueServerStateIds;
import com.sun.faces.util.FacesLogger;
import com.sun.faces.util.TypedCollections;
import com.sun.faces.util.LRUMap;
import com.sun.faces.util.Util;
import com.sun.faces.util.RequestStateManager;

import static com.sun.faces.config.WebConfiguration.BooleanWebContextInitParameter.AutoCompleteOffOnViewState;
import static com.sun.faces.config.WebConfiguration.BooleanWebContextInitParameter.EnableViewStateIdRendering;
import static com.sun.faces.config.WebConfiguration.BooleanWebContextInitParameter.NamespaceParameters;
import static com.sun.faces.config.WebConfiguration.WebContextInitParameter.NumberOfLogicalViews;
import static com.sun.faces.config.WebConfiguration.WebContextInitParameter.NumberOfViews;
import com.sun.faces.config.WebConfiguration;
import java.util.Collections;
import javax.faces.render.ResponseStateManager;

/**
 * 

* This StateHelper provides the functionality associated with server-side state saving, * though in actuallity, it is a hybrid between client and server. *

*/ public class ServerSideStateHelper extends StateHelper { private static final Logger LOGGER = FacesLogger.APPLICATION.getLogger(); /** * Key to store the AtomicInteger used to generate * unique state map keys. */ public static final String STATEMANAGED_SERIAL_ID_KEY = ServerSideStateHelper.class.getName() + ".SerialId"; /** * The top level attribute name for storing the state structures within * the session. */ public static final String LOGICAL_VIEW_MAP = ServerSideStateHelper.class.getName() + ".LogicalViewMap"; /** * The number of logical views as configured by the user. */ protected final Integer numberOfLogicalViews; /** * The number of views as configured by the user. */ protected final Integer numberOfViews; /** * Flag determining how server state IDs are generated. */ protected boolean generateUniqueStateIds; /** * Flag determining whether or not javax.faces.ViewState should be namespaced. */ protected boolean namespaceParameters; /** * Used to generate unique server state IDs. */ protected final SecureRandom random; // ------------------------------------------------------------ Constructors /** * Construct a new ServerSideStateHelper instance. */ public ServerSideStateHelper() { numberOfLogicalViews = getIntegerConfigValue(NumberOfLogicalViews); numberOfViews = getIntegerConfigValue(NumberOfViews); WebConfiguration webConfig = WebConfiguration.getInstance(); generateUniqueStateIds = webConfig.isOptionEnabled(GenerateUniqueServerStateIds); if (generateUniqueStateIds) { // Construct secure RNG. random = new SecureRandom(); // Make sure SecureRandom will seed itself safely by generating a random byte. This assures that an // accidental invocation of setSeed will not break security. random.nextBytes(new byte[1]); } else { random = null; } namespaceParameters = webConfig.isOptionEnabled(NamespaceParameters); } // ------------------------------------------------ Methods from StateHelper /** *

* Stores the provided state within the session obtained from the provided * FacesContext *

* *

If stateCapture is null, the composite * key used to look up the actual and logical views will be written to * the client as a hidden field using the ResponseWriter * from the provided FacesContext.

* *

If stateCapture is not null, the composite * key will be appended to the StringBuilder without any markup * included or any content written to the client. */ public void writeState(FacesContext ctx, Object state, StringBuilder stateCapture) throws IOException { Util.notNull("context", ctx); String id; UIViewRoot viewRoot = ctx.getViewRoot(); if (!viewRoot.isTransient()) { if (!ctx.getAttributes().containsKey("com.sun.faces.ViewStateValue")) { Util.notNull("state", state); Object[] stateToWrite = (Object[]) state; ExternalContext externalContext = ctx.getExternalContext(); Object sessionObj = externalContext.getSession(true); Map sessionMap = externalContext.getSessionMap(); //noinspection SynchronizationOnLocalVariableOrMethodParameter synchronized (sessionObj) { Map logicalMap = TypedCollections.dynamicallyCastMap( (Map) sessionMap .get(LOGICAL_VIEW_MAP), String.class, Map.class); if (logicalMap == null) { logicalMap = Collections.synchronizedMap(new LRUMap(numberOfLogicalViews)); sessionMap.put(LOGICAL_VIEW_MAP, logicalMap); } Object structure = stateToWrite[0]; Object savedState = handleSaveState(stateToWrite[1]); String idInLogicalMap = (String) RequestStateManager.get(ctx, RequestStateManager.LOGICAL_VIEW_MAP); if (idInLogicalMap == null) { idInLogicalMap = ((generateUniqueStateIds) ? createRandomId() : createIncrementalRequestId(ctx)); } String idInActualMap = null; if(ctx.getPartialViewContext().isPartialRequest()){ // If partial request, do not change actual view Id, because page not actually changed. // Otherwise partial requests will soon overflow cache with values that would be never used. idInActualMap = (String) RequestStateManager.get(ctx, RequestStateManager.ACTUAL_VIEW_MAP); } if (null == idInActualMap) { idInActualMap = ((generateUniqueStateIds) ? createRandomId() : createIncrementalRequestId(ctx)); } Map actualMap = TypedCollections.dynamicallyCastMap( logicalMap.get(idInLogicalMap), String.class, Object[].class); if (actualMap == null) { actualMap = new LRUMap(numberOfViews); logicalMap.put(idInLogicalMap, actualMap); } id = idInLogicalMap + ':' + idInActualMap; Object[] stateArray = actualMap.get(idInActualMap); // reuse the array if possible if (stateArray != null) { stateArray[0] = structure; stateArray[1] = savedState; } else { actualMap.put(idInActualMap, new Object[]{ structure, savedState }); } // always call put/setAttribute as we may be in a clustered environment. sessionMap.put(LOGICAL_VIEW_MAP, logicalMap); ctx.getAttributes().put("com.sun.faces.ViewStateValue", id); } } else { id = (String) ctx.getAttributes().get("com.sun.faces.ViewStateValue"); } } else { id = "stateless"; } if (stateCapture != null) { stateCapture.append(id); } else { ResponseWriter writer = ctx.getResponseWriter(); writer.startElement("input", null); writer.writeAttribute("type", "hidden", null); String viewStateParam = ResponseStateManager.VIEW_STATE_PARAM; if ((namespaceParameters) && (viewRoot instanceof NamingContainer)) { String namingContainerId = viewRoot.getContainerClientId(ctx); if (namingContainerId != null) { viewStateParam = namingContainerId + viewStateParam; } } writer.writeAttribute("name", viewStateParam, null); if (webConfig.isOptionEnabled(EnableViewStateIdRendering)) { String viewStateId = Util.getViewStateId(ctx); writer.writeAttribute("id", viewStateId, null); } writer.writeAttribute("value", id, null); if (webConfig.isOptionEnabled(AutoCompleteOffOnViewState)) { writer.writeAttribute("autocomplete", "off", null); } writer.endElement("input"); writeClientWindowField(ctx, writer); writeRenderKitIdField(ctx, writer); } } /** *

Inspects the incoming request parameters for the standardized state * parameter name. In this case, the parameter value will be the composite * ID generated by ServerSideStateHelper#writeState(FacesContext, Object, StringBuilder).

* *

The composite key will be used to find the appropriate view within the * session obtained from the provided FacesContext */ public Object getState(FacesContext ctx, String viewId) { String compoundId = getStateParamValue(ctx); if (compoundId == null) { return null; } if ("stateless".equals(compoundId)) { return "stateless"; } int sep = compoundId.indexOf(':'); assert (sep != -1); assert (sep < compoundId.length()); String idInLogicalMap = compoundId.substring(0, sep); String idInActualMap = compoundId.substring(sep + 1); ExternalContext externalCtx = ctx.getExternalContext(); Object sessionObj = externalCtx.getSession(false); // stop evaluating if the session is not available if (sessionObj == null) { if (LOGGER.isLoggable(Level.FINE)) { LOGGER.log(Level.FINE, "Unable to restore server side state for view ID {0} as no session is available", viewId); } return null; } //noinspection SynchronizationOnLocalVariableOrMethodParameter synchronized (sessionObj) { Map logicalMap = (Map) externalCtx.getSessionMap() .get(LOGICAL_VIEW_MAP); if (logicalMap != null) { Map actualMap = (Map) logicalMap.get(idInLogicalMap); if (actualMap != null) { RequestStateManager.set(ctx, RequestStateManager.LOGICAL_VIEW_MAP, idInLogicalMap); Object[] restoredState = new Object[2]; Object[] state = (Object[]) actualMap.get(idInActualMap); if(state != null){ restoredState[0] = state[0]; restoredState[1] = state[1]; RequestStateManager.set(ctx, RequestStateManager.ACTUAL_VIEW_MAP, idInActualMap); if (state.length == 2 && state[1] != null) { restoredState[1] = handleRestoreState(state[1]); } } return restoredState; } } } return null; } // ------------------------------------------------------- Protected Methods /** *

Utility method for obtaining the Integer based configuration * values used to change the behavior of the ServerSideStateHelper. * @param param the paramter to parse * @return the Integer representation of the parameter value */ protected Integer getIntegerConfigValue(WebContextInitParameter param) { String noOfViewsStr = webConfig.getOptionValue(param); Integer value = null; try { value = Integer.valueOf(noOfViewsStr); } catch (NumberFormatException nfe) { String defaultValue = param.getDefaultValue(); if (LOGGER.isLoggable(Level.WARNING)) { LOGGER.log(Level.WARNING, "jsf.state.server.cannot.parse.int.option", new Object[] { param.getQualifiedName(), defaultValue} ); } try { value = Integer.valueOf(defaultValue); } catch (NumberFormatException ne) { if (LOGGER.isLoggable(Level.FINEST)) { LOGGER.log(Level.FINEST, "Unable to convert number", ne); } } } return value; } /** * @param state the object returned from UIView.processSaveState * @return If {@link com.sun.faces.config.WebConfiguration.BooleanWebContextInitParameter#SerializeServerStateDeprecated} is * true, serialize and return the state, otherwise, return * state unchanged. */ protected Object handleSaveState(Object state) { if (webConfig.isOptionEnabled(SerializeServerStateDeprecated) || webConfig.isOptionEnabled(SerializeServerState)) { ByteArrayOutputStream baos = new ByteArrayOutputStream(1024); ObjectOutputStream oas = null; try { oas = serialProvider .createObjectOutputStream(((compressViewState) ? new GZIPOutputStream(baos, 1024) : baos)); //noinspection NonSerializableObjectPassedToObjectStream oas.writeObject(state); oas.flush(); } catch (Exception e) { throw new FacesException(e); } finally { if (oas != null) { try { oas.close(); } catch (IOException ioe) { if (LOGGER.isLoggable(Level.FINEST)) { LOGGER.log(Level.FINEST, "Closing stream", ioe); } } } } return baos.toByteArray(); } else { return state; } } /** * @param state the state as it was stored in the session * @return an object that can be passed to UIViewRoot.processRestoreState. * If {@link com.sun.faces.config.WebConfiguration.BooleanWebContextInitParameter#SerializeServerStateDeprecated} de-serialize the * state prior to returning it, otherwise return state as is. */ protected Object handleRestoreState(Object state) { if (webConfig.isOptionEnabled(SerializeServerStateDeprecated) || webConfig.isOptionEnabled(SerializeServerState)) { ByteArrayInputStream bais = new ByteArrayInputStream((byte[]) state); ObjectInputStream ois = null; try { ois = serialProvider .createObjectInputStream(((compressViewState) ? new GZIPInputStream(bais, 1024) : bais)); return ois.readObject(); } catch (Exception e) { throw new FacesException(e); } finally { if (ois != null) { try { ois.close(); } catch (IOException ioe) { if (LOGGER.isLoggable(Level.FINEST)) { LOGGER.log(Level.FINEST, "Closing stream", ioe); } } } } } else { return state; } } /** * @param ctx the FacesContext for the current request * @return a unique ID for building the keys used to store * views within a session */ private String createIncrementalRequestId(FacesContext ctx) { Map sm = ctx.getExternalContext().getSessionMap(); AtomicInteger idgen = (AtomicInteger) sm.get(STATEMANAGED_SERIAL_ID_KEY); if (idgen == null) { idgen = new AtomicInteger(1); } // always call put/setAttribute as we may be in a clustered environment. sm.put(STATEMANAGED_SERIAL_ID_KEY, idgen); return (UIViewRoot.UNIQUE_ID_PREFIX + idgen.getAndIncrement()); } private String createRandomId() { return Long.valueOf(random.nextLong()).toString(); } /** * Is stateless. * * @param facesContext the Faces context. * @param viewId the view id. * @return true if stateless, false otherwise. * @throws IllegalStateException when the request was not a postback. */ @Override public boolean isStateless(FacesContext facesContext, String viewId) throws IllegalStateException { if (facesContext.isPostback()) { Object stateObject = getState(facesContext, viewId); if (stateObject instanceof String && "stateless".equals((String) stateObject)) { return true; } return false; } throw new IllegalStateException("Cannot determine whether or not the request is stateless"); } }