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

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

/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 1997-2010 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 com.sun.faces.config.WebConfiguration.BooleanWebContextInitParameter;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.io.Writer;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;

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

import static com.sun.faces.config.WebConfiguration.WebContextInitParameter.ClientStateTimeout;
import static com.sun.faces.config.WebConfiguration.WebContextInitParameter.ClientStateWriteBufferSize;
import com.sun.faces.io.Base64InputStream;
import com.sun.faces.io.Base64OutputStreamWriter;
import com.sun.faces.util.FacesLogger;

/**
 * 

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

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

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

* * @see {@link com.sun.faces.config.WebConfiguration.WebEnvironmentEntry#ClientStateSavingPassword} */ 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; // ------------------------------------------------------------ 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 {@link com.sun.faces.renderkit.StateHelper#writeState(javax.faces.context.FacesContext, Object, StringBuilder)} */ public void writeState(FacesContext ctx, Object state, StringBuilder stateCapture) throws IOException { if (stateCapture != null) { doWriteState(state, new StringBuilderWriter(stateCapture)); } else { ResponseWriter writer = ctx.getResponseWriter(); writer.write(stateFieldStart); doWriteState(state, writer); writer.write(stateFieldEnd); 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 {@link com.sun.faces.renderkit.ServerSideStateHelper#writeState(javax.faces.context.FacesContext, Object, StringBuilder)}.

* *

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

* * @see {@link com.sun.faces.renderkit.StateHelper#getState(javax.faces.context.FacesContext, String)} */ public Object getState(FacesContext ctx, String viewId) throws IOException { String stateString = getStateParamValue(ctx); if (stateString == null) { return null; } return doGetState(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(String stateString) { ObjectInputStream ois = null; InputStream bis = new Base64InputStream(stateString); try { if (guard != null) { byte[] bytes = stateString.getBytes(); int numRead = bis.read(bytes, 0, bytes.length); byte[] decodedBytes = new byte[numRead]; bis.reset(); bis.read(decodedBytes, 0, decodedBytes.length); bytes = guard.decrypt(decodedBytes); if (bytes == null) return null; bis = new ByteArrayInputStream(bytes); } if (compressViewState) { bis = new GZIPInputStream(bis); } 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 (java.io.OptionalDataException ode) { if (LOGGER.isLoggable(Level.SEVERE)) { LOGGER.log(Level.SEVERE, ode.getMessage(), ode); } throw new FacesException(ode); } catch (ClassNotFoundException cnfe) { if (LOGGER.isLoggable(Level.SEVERE)) { LOGGER.log(Level.SEVERE, cnfe.getMessage(), cnfe); } throw new FacesException(cnfe); } 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) { // ignore } } } } /** * Serializes and Base64 encodes the provided state to the * provided writer/ * * @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(Object state, Writer writer) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); OutputStream base = null; 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; //noinspection NonSerializableObjectPassedToObjectStream oos.writeObject(stateToWrite[0]); //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(bytes); } // Base 64 encode Base64OutputStreamWriter bos = new Base64OutputStreamWriter(bytes.length, writer); bos.write(bytes, 0, bytes.length); bos.finish(); if (LOGGER.isLoggable(Level.FINE)) { LOGGER.log(Level.FINE, "Client State: total number of characters written: {0}", bos.getTotalCharsWritten()); } } finally { if (oos != null) { try { oos.close(); } catch (IOException ioe) { // ignore } } } } /** *

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.isSet(BooleanWebContextInitParameter.DisableClientStateEncryption)) { guard = new ByteArrayGuard(); } else { if (LOGGER.isLoggable(Level.FINE)) { LOGGER.log(Level.FINE, "jsf.config.webconfig.enventry.clientencrypt"); } } stateTimeoutEnabled = webConfig.isSet(ClientStateTimeout); if (stateTimeoutEnabled) { String timeout = webConfig.getOptionValue(ClientStateTimeout); try { stateTimeout = Long.parseLong(timeout); } catch (NumberFormatException nfe) { stateTimeout = Long.parseLong(ClientStateTimeout.getDefaultValue()); } } 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, "jsf.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, "jsf.renderkit.resstatemgr.clientbuf_not_integer", new Object[]{ ClientStateWriteBufferSize.getQualifiedName(), size, defaultSize}); } csBuffSize = Integer.parseInt(defaultSize); } } // ----------------------------------------------------------- Inner Classes /** * A simple Writer implementation to encapsulate a * StringBuilder instance. */ protected static final class StringBuilderWriter extends Writer { private 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; } public void write(char cbuf[], int off, int len) throws IOException { sb.append(cbuf, off, len); } public void flush() throws IOException { //no-op } public void close() throws IOException { //no-op } } // END StringBuilderWriter }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy