Package com.sun.faces.renderkit

Source Code of com.sun.faces.renderkit.ClientSideStateHelper$StringBuilderWriter

/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 1997-2012 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.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.config.WebConfiguration.WebEnvironmentEntry.ClientStateSavingPassword;
import com.sun.faces.io.Base64InputStream;
import com.sun.faces.io.Base64OutputStreamWriter;
import com.sun.faces.util.DebugObjectOutputStream;
import com.sun.faces.util.DebugUtil;
import com.sun.faces.util.FacesLogger;
import java.io.InvalidClassException;
import java.util.Map;
import javax.faces.render.ResponseStateManager;

/**
* <p>
* This <code>StateHelper</code> provides the functionality associated with client-side state saving.
* </p>
*/
public class ClientSideStateHelper extends StateHelper {

    private static final Logger LOGGER = FacesLogger.APPLICATION.getLogger();

    /**
     * <p>
     * Enabled encryption of view state.  Encryption is disabled by default.
     * </p>
     *
     * @see {@link com.sun.faces.config.WebConfiguration.WebEnvironmentEntry#ClientStateSavingPassword}
     */
    private ByteArrayGuard guard;

    /**
     * <p>
     * Flag indicating whether or not client view state will be manipulated
     * for and checked against a configured timeout value.
     * </p>
     *
     * <p>
     * This flag is configured via the <code>WebContextInitParameter.ClientStateTimeout</code>
     * configuration option of <code>WebConfiguration</code> and is disabled by
     * default.
     * </p>
     *
     * @see {@link com.sun.faces.config.WebConfiguration.WebContextInitParameter#ClientStateTimeout}
     */
    private boolean stateTimeoutEnabled;

    /**
     * <p>
     * If <code>stateTimeoutEnabled</code> is <code>true</code> this value will
     * represent the time in seconds that a particular client view state is
     * valid for.
     * </p>
     *
     * @see {@link com.sun.faces.config.WebConfiguration.WebContextInitParameter#ClientStateTimeout}
     */
    private long stateTimeout;

    /**
     * <p>
     * Client state is generally large, so this allows some tuning to control
     * the buffer that's used to write the client state.
     * </p>
     *
     * <p>
     * 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).
     * </p>
     *
     * @see {@link com.sun.faces.config.WebConfiguration.WebContextInitParameter#ClientStateWriteBufferSize}
     */
    private int csBuffSize;
   
   
    private boolean debugSerializedState;


    // ------------------------------------------------------------ Constructors


    /**
     * Construct a new <code>ClientSideStateHelper</code> instance.
     */
    public ClientSideStateHelper() {

        init();
       
    }


    // ------------------------------------------------ Methods from StateHelper


    /**
     * <p>
     * Writes the view state as a String generated by Base64 encoding the
     * Java Serialziation representation of the provided <code>state</code>
     * </p>
     *
     * <p>If <code>stateCapture</code> is <code>null</code>, the Base64 encoded
     * state will be written to the client as a hidden field using the <code>ResponseWriter</code>
     * from the provided <code>FacesContext</code>.</p>
     *
     * <p>If <code>stateCapture</code> is not <code>null</code>, the Base64 encoded
     * state will be appended to the provided <code>StringBuilder</code> without any markup
     * included or any content written to the client.
     *
     * @see StateHelper#writeState(javax.faces.context.FacesContext, java.lang.Object, java.lang.StringBuilder)
     */
    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", ResponseStateManager.VIEW_STATE_PARAM, null);
            if (webConfig.isOptionEnabled(EnableViewStateIdRendering)) {
                writer.writeAttribute("id", ResponseStateManager.VIEW_STATE_PARAM, 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");           
            writeRenderKitIdField(ctx, writer);
        }

    }


    /**
     * <p>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).</p>
     *
     * <p>The string will be Base64-decoded and the state reconstructed using standard
     * Java serialization.</p>
     *
     * @see StateHelper#getState(javax.faces.context.FacesContext, java.lang.String)
     */
    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(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 <code>stateString</code>
     */
    protected Object doGetState(String stateString) {
       
        if ("stateless".equals(stateString)) {
            return null;
        }
       
        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 (InvalidClassException ice) {
            /*
             * Thrown when the JSF 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 <code>state</code> to the
     * provided <code>writer</code>/
     *
     * @param facesContext the Faces context.
     * @param state view state
     * @param writer the <code>Writer</code> 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();
        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;

           
            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(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) {
                    if (LOGGER.isLoggable(Level.FINEST)) {
                        LOGGER.log(Level.FINEST, "Closing stream", ioe);
                    }
                }
            }
        }
    }

    /**
     * <p>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 <code>true</code>.  If the client state hasn't expired,
     * or the init parameter wasn't set, return <code>false</code>.
     * @param stateTime the time in milliseconds that the state was written
     *  to the client
     * @return <code>false</code> if the client state hasn't timed out, otherwise
     *  return <code>true</code>
     */
    protected boolean hasStateExpired(long stateTime) {

        if (stateTimeoutEnabled) {
            long elapsed = (System.currentTimeMillis() - stateTime) / 60000;
            return (elapsed > stateTimeout);
        } else {
            return false;
        }

    }


    /**
     * <p>
     * Initialze the various configuration options for client-side
     * sate saving.
     * </p>
     */
    protected void init() {

        String pass = webConfig.getEnvironmentEntry(
              ClientStateSavingPassword);
        if (pass != null) {
            guard = new ByteArrayGuard();
        }

        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);
        }

        debugSerializedState = webConfig.isOptionEnabled(BooleanWebContextInitParameter.EnableClientStateDebugging);

    }


    // ----------------------------------------------------------- Inner Classes


    /**
     * A simple <code>Writer</code> implementation to encapsulate a
     * <code>StringBuilder</code> 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
}
TOP

Related Classes of com.sun.faces.renderkit.ClientSideStateHelper$StringBuilderWriter

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.