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

com.sun.faces.renderkit.ClientSideStateHelper 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.2
Show newest version
/*
 * Copyright (c) 1997, 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.renderkit;

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.WebContextInitParameter.ClientStateTimeout;
import static com.sun.faces.config.WebConfiguration.WebContextInitParameter.ClientStateWriteBufferSize;
import static com.sun.faces.renderkit.RenderKitUtils.PredefinedPostbackParameter.VIEW_STATE_PARAM;
import static java.util.logging.Level.WARNING;

import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InvalidClassException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OptionalDataException;
import java.io.OutputStream;
import java.io.Writer;
import java.util.Base64;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;

import com.sun.faces.RIConstants;
import com.sun.faces.config.WebConfiguration.BooleanWebContextInitParameter;
import com.sun.faces.util.DebugObjectOutputStream;
import com.sun.faces.util.DebugUtil;
import com.sun.faces.util.FacesLogger;
import com.sun.faces.util.Util;

import jakarta.faces.FacesException;
import jakarta.faces.context.FacesContext;
import jakarta.faces.context.ResponseWriter;

/**
 * 

* This StateHelper provides the functionality associated with client-side state saving. *

*/ public class ClientSideStateHelper extends StateHelper { private static final Logger LOGGER = FacesLogger.APPLICATION.getLogger(); public static final String STATELESS = "stateless"; /** *

* Enabled encryption of view state. Encryption is disabled by default. *

*/ private ByteArrayGuard guard; /** *

* Flag indicating whether or not client view state will be manipulated for and checked against a configured timeout * value. *

* *

* This flag is configured via the WebContextInitParameter.ClientStateTimeout configuration option of * WebConfiguration and is disabled by default. *

* * @see {@link com.sun.faces.config.WebConfiguration.WebContextInitParameter#ClientStateTimeout} */ private boolean stateTimeoutEnabled; /** *

* If stateTimeoutEnabled is true this value will represent the time in seconds that a * particular client view state is valid for. *

* * @see {@link com.sun.faces.config.WebConfiguration.WebContextInitParameter#ClientStateTimeout} */ private long stateTimeout; /** *

* Client state is generally large, so this allows some tuning to control the buffer that's used to write the client * state. *

* *

* The value specified must be divisable by two as the buffer is split between character and bytes (due to how client * state is written). By default, the buffer size is 8192 (per request). *

* * @see {@link com.sun.faces.config.WebConfiguration.WebContextInitParameter#ClientStateWriteBufferSize} */ private int csBuffSize; private boolean debugSerializedState; // ------------------------------------------------------------ Constructors /** * Construct a new ClientSideStateHelper instance. */ public ClientSideStateHelper() { init(); } // ------------------------------------------------ Methods from StateHelper /** *

* Writes the view state as a String generated by Base64 encoding the Java Serialziation representation of the provided * state *

* *

* If stateCapture is null, the Base64 encoded state will be written to the client as a hidden * field using the ResponseWriter from the provided FacesContext. *

* *

* If stateCapture is not null, the Base64 encoded state will be appended to the provided * StringBuilder without any markup included or any content written to the client. * * @see StateHelper#writeState(jakarta.faces.context.FacesContext, java.lang.Object, java.lang.StringBuilder) */ @Override public void writeState(FacesContext ctx, Object state, StringBuilder stateCapture) throws IOException { if (stateCapture != null) { doWriteState(ctx, state, new StringBuilderWriter(stateCapture)); } else { ResponseWriter writer = ctx.getResponseWriter(); writer.startElement("input", null); writer.writeAttribute("type", "hidden", null); writer.writeAttribute("name", VIEW_STATE_PARAM.getName(ctx), null); if (webConfig.isOptionEnabled(EnableViewStateIdRendering)) { String viewStateId = Util.getViewStateId(ctx); writer.writeAttribute("id", viewStateId, null); } StringBuilder stateBuilder = new StringBuilder(); doWriteState(ctx, state, new StringBuilderWriter(stateBuilder)); writer.writeAttribute("value", stateBuilder.toString(), 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 a Base64 encoded string previously encoded by ServerSideStateHelper#writeState(FacesContext, Object, * StringBuilder). *

* *

* The string will be Base64-decoded and the state reconstructed using standard Java serialization. *

* * @see StateHelper#getState(jakarta.faces.context.FacesContext, java.lang.String) */ @Override public Object getState(FacesContext ctx, String viewId) throws IOException { String stateString = getStateParamValue(ctx); if (stateString == null) { return null; } if (STATELESS.equals(stateString)) { return STATELESS; } return doGetState(ctx, stateString); } // ------------------------------------------------------- Protected Methods /** * Rebuilds the view state from the Base64 included String included with the request. * * @param stateString the Base64 encoded view state * @return the view state reconstructed from stateString */ protected Object doGetState(FacesContext ctx, String stateString) { if (STATELESS.equals(stateString)) { return null; } ObjectInputStream ois = null; InputStream bis = null; try { if (guard != null) { byte[] bytes = stateString.getBytes(RIConstants.CHAR_ENCODING); byte[] decodedBytes = Base64.getDecoder().decode(bytes); bytes = guard.decrypt(ctx, decodedBytes); if (bytes == null) { return null; } bis = new ByteArrayInputStream(bytes); } if (null != bis && compressViewState) { bis = new GZIPInputStream(bis); } if (null == bis) { throw new FacesException("Unable to encode stateString"); } ois = serialProvider.createObjectInputStream(bis); long stateTime = 0; if (stateTimeoutEnabled) { try { stateTime = ois.readLong(); } catch (IOException ioe) { // we've caught an exception trying to read the time // marker. This most likely means a view that has been // around before upgrading to the release that included // this feature. So, no marker, return null now to // cause a ViewExpiredException if (LOGGER.isLoggable(Level.FINE)) { LOGGER.fine("Client state timeout is enabled, but unable to find the " + "time marker in the serialized state. Assuming state " + "to be old and returning null."); } return null; } } Object structure = ois.readObject(); Object state = ois.readObject(); if (stateTime != 0 && hasStateExpired(stateTime)) { // return null if state has expired. This should cause // a ViewExpiredException to be thrown return null; } return new Object[] { structure, state }; } catch (OptionalDataException | ClassNotFoundException ode) { if (LOGGER.isLoggable(Level.SEVERE)) { LOGGER.log(Level.SEVERE, ode.getMessage(), ode); } throw new FacesException(ode); } catch (InvalidClassException ice) { /* * Thrown when the Faces runtime is trying to deserialize a client-side state that has been saved with a previous version * of Mojarra. Instead of blowing up, force a ViewExpiredException. */ return null; } catch (IOException iox) { if (LOGGER.isLoggable(Level.SEVERE)) { LOGGER.log(Level.SEVERE, iox.getMessage(), iox); } throw new FacesException(iox); } finally { if (ois != null) { try { ois.close(); } catch (IOException ioe) { if (LOGGER.isLoggable(Level.FINEST)) { LOGGER.log(Level.FINEST, "Closing stream", ioe); } } } } } /** * Serializes and Base64 encodes the provided state to the provided writer/ * * @param facesContext the Faces context. * @param state view state * @param writer the Writer to write the content to * @throws IOException if an error occurs writing the state to the client */ protected void doWriteState(FacesContext facesContext, Object state, Writer writer) throws IOException { if (facesContext.getViewRoot().isTransient()) { writer.write(STATELESS); writer.flush(); return; } ByteArrayOutputStream baos = new ByteArrayOutputStream(); final OutputStream base; if (compressViewState) { base = new GZIPOutputStream(baos, csBuffSize); } else { base = baos; } ObjectOutputStream oos = null; try { oos = serialProvider.createObjectOutputStream(new BufferedOutputStream(base)); if (stateTimeoutEnabled) { oos.writeLong(System.currentTimeMillis()); } Object[] stateToWrite = (Object[]) state; if (debugSerializedState) { ByteArrayOutputStream discard = new ByteArrayOutputStream(); DebugObjectOutputStream out = new DebugObjectOutputStream(discard); try { out.writeObject(stateToWrite[0]); } catch (Exception e) { throw new FacesException("Serialization error. Path to offending instance: " + out.getStack(), e); } } // noinspection NonSerializableObjectPassedToObjectStream oos.writeObject(stateToWrite[0]); if (debugSerializedState) { ByteArrayOutputStream discard = new ByteArrayOutputStream(); DebugObjectOutputStream out = new DebugObjectOutputStream(discard); try { out.writeObject(stateToWrite[1]); } catch (Exception e) { DebugUtil.printState((Map) stateToWrite[1], LOGGER); throw new FacesException("Serialization error. Path to offending instance: " + out.getStack(), e); } } // noinspection NonSerializableObjectPassedToObjectStream oos.writeObject(stateToWrite[1]); oos.flush(); oos.close(); oos = null; // get bytes for encrypting byte[] bytes = baos.toByteArray(); if (guard != null) { // this will MAC bytes = guard.encrypt(facesContext, bytes); } // Base 64 encode String encodedBytes = new String(Base64.getEncoder().encode(bytes)); writer.write(encodedBytes); if (LOGGER.isLoggable(Level.FINE)) { LOGGER.log(Level.FINE, "Client State: total number of characters written: {0}", encodedBytes.length()); } } finally { if (oos != null) { try { oos.close(); } catch (IOException ioe) { if (LOGGER.isLoggable(Level.FINEST)) { LOGGER.log(Level.FINEST, "Closing stream", ioe); } } } } } /** *

* If the {@link com.sun.faces.config.WebConfiguration.WebContextInitParameter#ClientStateTimeout} init parameter is * set, calculate the elapsed time between the time the client state was written and the time this method was invoked * during restore. If the client state has expired, return true. If the client state hasn't expired, or the * init parameter wasn't set, return false. * * @param stateTime the time in milliseconds that the state was written to the client * @return false if the client state hasn't timed out, otherwise return true */ protected boolean hasStateExpired(long stateTime) { if (stateTimeoutEnabled) { long elapsed = (System.currentTimeMillis() - stateTime) / 60000; return elapsed > stateTimeout; } else { return false; } } /** *

* Initialze the various configuration options for client-side sate saving. *

*/ protected void init() { if (webConfig.canProcessJndiEntries() && !webConfig.isSet(BooleanWebContextInitParameter.DisableClientStateEncryption)) { guard = new ByteArrayGuard(); } else { if (LOGGER.isLoggable(Level.FINE)) { LOGGER.log(Level.FINE, "faces.config.webconfig.enventry.clientencrypt"); } } stateTimeoutEnabled = webConfig.isSet(ClientStateTimeout); if (stateTimeoutEnabled) { String timeout = webConfig.getOptionValue(ClientStateTimeout); try { stateTimeout = Long.parseLong(timeout); if (stateTimeout < 0) { stateTimeoutEnabled = false; } } catch (NumberFormatException nfe) { if (LOGGER.isLoggable(WARNING)) { LOGGER.log(WARNING, ClientStateTimeout.getQualifiedName() + " context param value of '" + timeout + "' is not parseable as Long, it will be ignored"); } stateTimeoutEnabled = false; } } String size = webConfig.getOptionValue(ClientStateWriteBufferSize); String defaultSize = ClientStateWriteBufferSize.getDefaultValue(); try { csBuffSize = Integer.parseInt(size); if (csBuffSize % 2 != 0) { if (LOGGER.isLoggable(Level.WARNING)) { LOGGER.log(Level.WARNING, "faces.renderkit.resstatemgr.clientbuf_div_two", new Object[] { ClientStateWriteBufferSize.getQualifiedName(), size, defaultSize }); } csBuffSize = Integer.parseInt(defaultSize); } else { csBuffSize /= 2; if (LOGGER.isLoggable(Level.FINE)) { LOGGER.fine("Using client state buffer size of " + csBuffSize); } } } catch (NumberFormatException nfe) { if (LOGGER.isLoggable(Level.WARNING)) { LOGGER.log(Level.WARNING, "faces.renderkit.resstatemgr.clientbuf_not_integer", new Object[] { ClientStateWriteBufferSize.getQualifiedName(), size, defaultSize }); } csBuffSize = Integer.parseInt(defaultSize); } debugSerializedState = webConfig.isOptionEnabled(BooleanWebContextInitParameter.EnableClientStateDebugging); } /** * 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; try { stateObject = getState(facesContext, viewId); } catch (IOException ioe) { throw new IllegalStateException("Cannot determine whether or not the request is stateless", ioe); } return STATELESS.equals(stateObject); } throw new IllegalStateException("Cannot determine whether or not the request is stateless"); } // ----------------------------------------------------------- Inner Classes /** * A simple Writer implementation to encapsulate a StringBuilder instance. */ protected static final class StringBuilderWriter extends Writer { private final StringBuilder sb; // -------------------------------------------------------- Constructors protected StringBuilderWriter(StringBuilder sb) { this.sb = sb; } // ------------------------------------------------- Methods from Writer @Override public void write(int c) throws IOException { sb.append((char) c); } @Override public void write(char[] cbuf) throws IOException { sb.append(cbuf); } @Override public void write(String str) throws IOException { sb.append(str); } @Override public void write(String str, int off, int len) throws IOException { sb.append(str.toCharArray(), off, len); } @Override public Writer append(CharSequence csq) throws IOException { sb.append(csq); return this; } @Override public Writer append(CharSequence csq, int start, int end) throws IOException { sb.append(csq, start, end); return this; } @Override public Writer append(char c) throws IOException { sb.append(c); return this; } @Override public void write(char[] cbuf, int off, int len) throws IOException { sb.append(cbuf, off, len); } @Override public void flush() throws IOException { // no-op } @Override public void close() throws IOException { // no-op } } // END StringBuilderWriter }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy