dev@javaserverfaces.java.net

[REVIEW] Performance enhancements to ResponseStateManagerImpl

From: Ryan Lubke <Ryan.Lubke_at_Sun.COM>
Date: Wed, 31 May 2006 11:40:51 -0700

ResponseStateManagerImpl improvements. Bencharking
shows an increase of 20-22 requests per second using
100 client threads.


SECTION: Modified Files
----------------------------
M src/com/sun/faces/LogStrings.properties
 - new log messages

M src/com/sun/faces/config/WebConfiguration.java
 - added new configuration option 'com.sun.faces.clientStateWriteBufferSize'
   this option specifies the size of a split buffer for
   storing state bytes and another buffer for storing the
   base64 converted chars. Default is 8192 bytes.

M src/com/sun/faces/renderkit/ByteArrayGuard.java
 - ByteArrayGuard no longer handles the encryption/decription.
   It now is merely a container for the encrypt/decrypt
   ciphers. As far as the actual encryption/decryption
   operations, this is handled by chaining streams.

M src/com/sun/faces/renderkit/ResponseStateManagerImpl.java
 - streamlined implementation for restore and writing state
   based on the changes above and the new streams added
   below.

M
systest/src/com/sun/faces/systest/render/CustomResponseStateManagerImpl.java
 - modified to refer to systest version of Base64.
 - removed reference to ByteArrayGuard

A src/com/sun/faces/io/Base64InputStream.java
 - A base stream which provides the base64 decoded bytes
   of the state request parameter.

A src/com/sun/faces/io/Base64OutputStreamWriter.java
 - OutputStream what wraps a writer. The bytes
   are written to a buffer in the stream, which are
   then converted to Base64 chars and stored in
   another buffer. When this buffer is full, it will
   flush to the wrapped writer.

A systest/src/com/sun/faces/systest/render/Base64.java
 - Moved from jsf-ri/src/com/sun/faces/util

R src/com/sun/faces/util/Base64.java
 - Moved to jsf-ri/systest/src/com/sun/faces/systest/render


SECTION: Diffs
----------------------------
Index: src/com/sun/faces/LogStrings.properties
===================================================================
RCS file:
/cvs/javaserverfaces-sources/jsf-ri/src/com/sun/faces/LogStrings.properties,v
retrieving revision 1.16
diff -u -r1.16 LogStrings.properties
--- src/com/sun/faces/LogStrings.properties 22 May 2006 22:38:06
-0000 1.16
+++ src/com/sun/faces/LogStrings.properties 31 May 2006 18:25:40 -0000
@@ -62,3 +62,5 @@
 jsf.spi.injection.provider_cannot_instantiate=JSF1031: The specified
InjectionProvider ''{0}'' cannot be instantiated.
 jsf.spi.injection.provider_configured=JSF1032: Resource injection
ENABLED using InjectionProvider ''{0}''.
 jsf.spi.injection.no_injection=JSF1033: Resource injection is DISABLED.
+jsf.renderkit.resstatemgr.clientbuf_div_two=JSF1034: The value ''{1}''
for ''{0}'' must be evenly divisable by 2. Defaulting to ''{3}''.
+jsf.renderkit.resstatemgr.clientbuf_not_integer=JSF1035: The value
''{1}'' for ''{0}'' is not an Integer. Defaulting to ''{3]''.
Index: src/com/sun/faces/config/WebConfiguration.java
===================================================================
RCS file:
/cvs/javaserverfaces-sources/jsf-ri/src/com/sun/faces/config/WebConfiguration.java,v
retrieving revision 1.6
diff -u -r1.6 WebConfiguration.java
--- src/com/sun/faces/config/WebConfiguration.java 31 May 2006
17:22:30 -0000 1.6
+++ src/com/sun/faces/config/WebConfiguration.java 31 May 2006
18:25:40 -0000
@@ -467,6 +467,10 @@
         ResponseBufferSize(
               "com.sun.faces.responseBufferSize",
               "4096"
+ ),
+ ClientStateWriteBufferSize(
+ "com.sun.faces.clientStateWriteBufferSize",
+ "8192"
         );
 
         private String defaultValue;
Index: src/com/sun/faces/renderkit/ByteArrayGuard.java
===================================================================
RCS file:
/cvs/javaserverfaces-sources/jsf-ri/src/com/sun/faces/renderkit/ByteArrayGuard.java,v
retrieving revision 1.12
diff -u -r1.12 ByteArrayGuard.java
--- src/com/sun/faces/renderkit/ByteArrayGuard.java 5 Apr 2006
17:53:42 -0000 1.12
+++ src/com/sun/faces/renderkit/ByteArrayGuard.java 31 May 2006
18:25:41 -0000
@@ -30,247 +30,101 @@
 package com.sun.faces.renderkit;
 
 import javax.crypto.Cipher;
-import javax.crypto.Mac;
+import javax.crypto.NullCipher;
 import javax.crypto.SecretKeyFactory;
 import javax.crypto.spec.DESedeKeySpec;
 import javax.crypto.spec.IvParameterSpec;
-import javax.crypto.spec.SecretKeySpec;
 import javax.faces.FacesException;
 
-import java.io.IOException;
 import java.security.Key;
 import java.security.MessageDigest;
 import java.security.SecureRandom;
-import java.util.Arrays;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
 import com.sun.faces.util.Util;
 
 /**
- * This utility class provides services to encrypt or decrypt a byte array.
- * The algorithm used to encrypt byte array is 3DES with CBC
- * The algorithm used to create the message authentication code (MAC)
is SHA1
+ * <p>This utility class is to provide both encryption and
+ * decryption <code>Ciphers</code> to <code>ResponseStateManager</code>
+ * implementations wishing to provide encryption support.</p>
  *
- * Original author Inderjeet Singh, J2EE Blue Prints Team. Modified to
suit JSF
- * needs.
+ * <p>The algorithm used to encrypt byte array is 3DES with CBC.</p>
+ *
+ * <p>Original author Inderjeet Singh, J2EE Blue Prints Team. Modified
to suit JSF
+ * needs.</p>
  */
 public final class ByteArrayGuard {
 
 
      // Log instance for this class
- private static final Logger logger =
+ private static final Logger LOGGER =
             Util.getLogger(Util.FACES_LOGGER + Util.RENDERKIT_LOGGER);
- private static final int DEFAULT_IV_LENGTH = 8;
- private static final int DEFAULT_KEY_LENGTH = 24;
- private static final int DEFAULT_MAC_LENGTH = 20;
+ private static final int IV_LENGTH = 8;
+ private static final int KEY_LENGTH = 24;
+
+ private static Cipher NULL_CIPHER = new NullCipher();
     
- private final Object decLock = new Object();
- private final Object encLock = new Object();
- private final int ivLength;
- private final int keyLength;
- private final int macLength;
-
- private Cipher decryptCipher = null;
- private Cipher encryptCipher = null;
- private SecretKeyFactory keygen = null;
- private SecureRandom prng = null;
- private byte[] PASSWORD_KEY = null;
- private byte[] iVector = null;
-
+ private Cipher decryptCipher = NULL_CIPHER;
+ private Cipher encryptCipher = NULL_CIPHER;
 
     // ------------------------------------------------------------
Constructors
 
 
     /**
      * Constructs a new <code>ByteArrayGuard</code> using the specified
- * <code>keyLength</code>, <code>macLength</code>,
<code>ivLength</code>.
- * @param keyLength the length of the key used for encryption
- * @param macLength the length of the message authentication used
- * @param ivLength length of the initialization vector used by the
block cipher
+ * <code>keyLength</code>, <code>macLength</code>,
<code>ivLength</code>.
      * @param password the password to seed the encryption
      */
- private ByteArrayGuard(int keyLength,
- int macLength,
- int ivLength,
- String password) {
-
- this.keyLength = keyLength;
- this.macLength = macLength;
- this.ivLength = ivLength;
-
+ public ByteArrayGuard(String password) {
+
         if (password != null) {
- if (logger.isLoggable(Level.FINE)) {
- logger.log(Level.FINE,
+ if (LOGGER.isLoggable(Level.FINE)) {
+ LOGGER.log(Level.FINE,
                            "Client state saving encryption enabled.");
             }
- PASSWORD_KEY = convertPasswordToKey(password.getBytes());
+ byte[] passwordKey =
convertPasswordToKey(password.getBytes());
             try {
- prng = SecureRandom.getInstance("SHA1PRNG");
- keygen = SecretKeyFactory.getInstance("DESede");
+ SecureRandom prng = SecureRandom.getInstance("SHA1PRNG");
+ SecretKeyFactory keygen =
SecretKeyFactory.getInstance("DESede");
                 encryptCipher =
- getBlockCipherForEncryption(PASSWORD_KEY);
- iVector = encryptCipher.getIV();
+ getBlockCipherForEncryption(keygen, prng,
passwordKey);
+ byte[] iVector = encryptCipher.getIV();
                 decryptCipher =
- getBlockCipherForDecryption(PASSWORD_KEY,
+ getBlockCipherForDecryption(keygen,
+ prng,
+ passwordKey,
                                                   iVector);
             } catch (Exception e) {
- if (logger.isLoggable(Level.SEVERE)) {
- logger.log(Level.SEVERE,
+ if (LOGGER.isLoggable(Level.SEVERE)) {
+ LOGGER.log(Level.SEVERE,
                                "Unexpected exception initializing
encryption."
                                + " No encryption will be performed.",
                                e);
- }
- PASSWORD_KEY = null;
- keygen = null;
- encryptCipher = null;
- decryptCipher = null;
- iVector = null;
- prng = null;
+ }
+
+ encryptCipher = NULL_CIPHER;
+ decryptCipher = NULL_CIPHER;
+
             }
         }
     }
 
 
- // ----------------------------------------------------------
Public Methods
+ // ----------------------------------------------------------
Public Methods
 
 
- public static ByteArrayGuard newInstance() {
-
- return new ByteArrayGuard(DEFAULT_KEY_LENGTH,
- DEFAULT_MAC_LENGTH,
- DEFAULT_IV_LENGTH,
- null);
-
+ public Cipher getEncryptionCipher() {
+ return encryptCipher;
     }
-
     
- public static ByteArrayGuard newInstance(String password) {
- return new ByteArrayGuard(DEFAULT_KEY_LENGTH,
- DEFAULT_MAC_LENGTH,
- DEFAULT_IV_LENGTH,
- password);
- }
-
-
- /**
- * Decrypts the specified byte array using the specified password, and
- * generates an inputstream from it. The file must be encrypted by the
- * above method for encryption. The method also verifies the MAC. It
- * uses the IV present in the file for decryption.
- * @param securedata The encrypted data (including mac and
initialization
- * vector) that needs to be decrypted
- * @return A byte array containing the decrypted contents
- */
- public byte[] decrypt(byte[] securedata) {
-
- if (PASSWORD_KEY != null) {
- try {
- // Extract MAC
- byte[] macBytes = new byte[macLength];
- System.arraycopy(securedata, 0, macBytes, 0,
macBytes.length);
- // Extract initialization vector used for encryption
- byte[] iv = new byte[ivLength];
- System.arraycopy(securedata, macBytes.length, iv, 0,
iv.length);
-
- // Extract encrypted data
- byte[] encdata =
- new byte[securedata.length - macBytes.length -
iv.length];
- System.arraycopy(securedata,
- macBytes.length + iv.length,
- encdata,
- 0,
- encdata.length);
-
- Mac mac = getMac(PASSWORD_KEY);
- // verify MAC by regenerating it and comparing it with
the received value
- mac.update(encdata);
- byte[] macBytesCalculated = mac.doFinal();
- if (Arrays.equals(macBytes, macBytesCalculated)) {
- // decrypt data only if the MAC was valid
- synchronized(decLock) {
- return decryptCipher.doFinal(encdata);
- }
- } else {
- throw new IOException(
- "Could not Decrypt Secure View State,
passwords did not match.");
- }
- } catch (Exception e) {
- if (logger.isLoggable(Level.SEVERE)) {
- logger.log(Level.SEVERE, e.getMessage(), e.getCause());
- }
- throw new FacesException(e);
- }
- } else {
- return securedata;
- }
-
- }
-
-
- /**
- * Encrypts the specified plaindata using the specified password.
It also
- * stores the MAC and the IV in the output. The 20-byte MAC is stored
- * first, followed by the 8-byte IV, followed by the encrypted
- * contents of the file.
- * @param plaindata The plain text that needs to be encrypted
- * @return The encrypted contents
- */
- public byte[] encrypt(byte[] plaindata) {
-
- if (PASSWORD_KEY != null) {
- try {
- // encrypt the plaintext
-
- byte[] encdata;
- synchronized(encLock) {
- encdata = encryptCipher.doFinal(plaindata);
- }
- Mac mac = getMac(PASSWORD_KEY);
- mac.update(encdata);
- // generate MAC
- byte[] macBytes = mac.doFinal();
-
- // concat byte arrays for MAC, IV, and encrypted data
- // Note that the order is important here. MAC and IV are
- // of fixed length and need to appear before the
encrypted data
- // for easy extraction while decrypting.
- byte[] tmp = concatBytes(macBytes, iVector);
- return concatBytes(tmp, encdata);
- } catch (Exception e) {
- if (logger.isLoggable(Level.SEVERE)) {
- logger.log(Level.SEVERE, e.getMessage(), e.getCause());
- }
- throw new FacesException(e);
- }
- } else {
- return plaindata;
- }
-
+ public Cipher getDecryptionCipher() {
+ return decryptCipher;
     }
-
+
 
     // ---------------------------------------------------------
Private Methods
-
-
- /**
- * This method concatenates two byte arrays.
- * @return a byte array of array1||array2
- * @param array1 first byte array to be concatenated
- * @param array2 second byte array to be concatenated
- */
- private static byte[] concatBytes(byte[] array1, byte[] array2) {
-
- byte[] cBytes = new byte[array1.length + array2.length];
- try {
- System.arraycopy(array1, 0, cBytes, 0, array1.length);
- System.arraycopy(array2, 0, cBytes, array1.length,
array2.length);
- } catch(Exception e) {
- throw new FacesException(e);
- }
- return cBytes;
-
- }
+
 
 
     /**
@@ -292,7 +146,7 @@
             SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
             random.setSeed(seed);
 
- byte[] rawkey = new byte[keyLength];
+ byte[] rawkey = new byte[KEY_LENGTH];
             random.nextBytes(rawkey);
             return rawkey;
         } catch (Exception e) {
@@ -304,20 +158,24 @@
 
     /**
      * Obtain a <code>Cipher</code> for decrypting data.
+ * @param keyGen
+ * @param random
      * @param rawKey must be 24 bytes in length.
- * @param iv initialization vector
- * @return a 3DES block cipher to be used for decryption based on the
+ * @param iv initialization vector @return a 3DES block cipher to
be used for decryption based on the
      * specified key
+ * @return an initialized <code>Cipher</code> for decryption
      */
- private Cipher getBlockCipherForDecryption(byte[] rawKey,
+ private Cipher getBlockCipherForDecryption(SecretKeyFactory keyGen,
+ SecureRandom random,
+ byte[] rawKey,
                                                byte[] iv) {
 
         try {
             DESedeKeySpec keyspec = new DESedeKeySpec(rawKey);
- Key key = keygen.generateSecret(keyspec);
+ Key key = keyGen.generateSecret(keyspec);
             Cipher cipher = Cipher.getInstance("DESede/CBC/PKCS5Padding");
             IvParameterSpec ivspec = new IvParameterSpec(iv);
- cipher.init(Cipher.DECRYPT_MODE, key, ivspec, prng);
+ cipher.init(Cipher.DECRYPT_MODE, key, ivspec, random);
             return cipher;
         } catch (Exception e) {
             throw new FacesException(e);
@@ -328,20 +186,24 @@
 
     /**
      * Obtain a <code>Cipher</code> for encrypting data.
- * @param rawKey must be 24 bytes in length.
- * @return a 3DES block cipher to be used for encryption based on the
+ * @param keyGen
+ * @param random
+ * @param rawKey must be 24 bytes in length. @return a 3DES block
cipher to be used for encryption based on the
      * specified key
+ * @return an initialized <code>Cipher</code> for decryption
      */
- private Cipher getBlockCipherForEncryption(byte[] rawKey) {
+ private Cipher getBlockCipherForEncryption(SecretKeyFactory keyGen,
+ SecureRandom random,
+ byte[] rawKey) {
 
         try {
             DESedeKeySpec keyspec = new DESedeKeySpec(rawKey);
- Key key = keygen.generateSecret(keyspec);
+ Key key = keyGen.generateSecret(keyspec);
             Cipher cipher = Cipher.getInstance("DESede/CBC/PKCS5Padding");
- byte[] iv = new byte[ivLength];
- prng.nextBytes(iv);
+ byte[] iv = new byte[IV_LENGTH];
+ random.nextBytes(iv);
             IvParameterSpec ivspec = new IvParameterSpec(iv);
- cipher.init(Cipher.ENCRYPT_MODE, key, ivspec, prng);
+ cipher.init(Cipher.ENCRYPT_MODE, key, ivspec, random);
             return cipher;
         } catch (Exception e) {
             throw new FacesException(e);
@@ -349,18 +211,5 @@
 
     }
 
-
- private Mac getMac(byte[] rawKey) {
-
- try {
- Mac lMac = Mac.getInstance("HmacSHA1");
- SecretKeySpec key = new SecretKeySpec(rawKey, 0, macLength,
"HmacSHA1");
- lMac.init(key);
- return lMac;
- } catch (Exception e) {
- throw new FacesException(e);
- }
-
- }
-
+
 }
Index: src/com/sun/faces/renderkit/ResponseStateManagerImpl.java
===================================================================
RCS file:
/cvs/javaserverfaces-sources/jsf-ri/src/com/sun/faces/renderkit/ResponseStateManagerImpl.java,v
retrieving revision 1.34
diff -u -r1.34 ResponseStateManagerImpl.java
--- src/com/sun/faces/renderkit/ResponseStateManagerImpl.java 22 May
2006 23:35:52 -0000 1.34
+++ src/com/sun/faces/renderkit/ResponseStateManagerImpl.java 31 May
2006 18:25:41 -0000
@@ -37,12 +37,14 @@
 import javax.faces.context.ResponseWriter;
 import javax.faces.render.RenderKitFactory;
 import javax.faces.render.ResponseStateManager;
+import javax.crypto.CipherInputStream;
+import javax.crypto.CipherOutputStream;
 
-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.util.logging.Level;
 import java.util.logging.Logger;
 import java.util.zip.GZIPInputStream;
@@ -51,7 +53,9 @@
 import com.sun.faces.config.WebConfiguration;
 import
com.sun.faces.config.WebConfiguration.BooleanWebContextInitParameter;
 import com.sun.faces.config.WebConfiguration.WebEnvironmentEntry;
-import com.sun.faces.util.Base64;
+import com.sun.faces.config.WebConfiguration.WebContextInitParameter;
+import com.sun.faces.io.Base64InputStream;
+import com.sun.faces.io.Base64OutputStreamWriter;
 import com.sun.faces.util.Util;
 
 
@@ -64,29 +68,34 @@
     // Log instance for this class
     private static final Logger LOGGER =
           Util.getLogger(Util.FACES_LOGGER + Util.RENDERKIT_LOGGER);
+
     private static final String FACES_VIEW_STATE =
           "com.sun.faces.FACES_VIEW_STATE";
 
+ private static final char[] STATE_FIELD_START =
+ ("<input type=\"hidden\" name=\""
+ + ResponseStateManager.VIEW_STATE_PARAM
+ + "\" id=\""
+ + ResponseStateManager.VIEW_STATE_PARAM
+ + "\" value=\"").toCharArray();
+
+ private static final char[] STATE_FIELD_END =
+ "\" />".toCharArray();
 
- private Boolean compressState = null;
- private ByteArrayGuard byteArrayGuard = null;
 
+ private Boolean compressState;
+ private ByteArrayGuard guard;
+ private int csBuffSize;
 
     public ResponseStateManagerImpl() {
 
- super();
- WebConfiguration webConfig = WebConfiguration.getInstance();
- assert(webConfig != null);
- byteArrayGuard = ByteArrayGuard
- .newInstance(webConfig.getEnvironmentEntry(
- WebEnvironmentEntry.ClientStateSavingPassword));
+ super();
+ init();
 
     }
 
     
- /**
- * @see {_at_link
ResponseStateManager#getComponentStateToRestore(javax.faces.context.FacesContext)}
 
- */
+ /** @see {_at_link
ResponseStateManager#getComponentStateToRestore(javax.faces.context.FacesContext)}
*/
     @Override
     @SuppressWarnings("Deprecation")
     public Object getComponentStateToRestore(FacesContext context) {
@@ -98,21 +107,17 @@
     }
 
 
- /**
- * @see {_at_link
ResponseStateManager#isPostback(javax.faces.context.FacesContext)}
- */
- @Override
+ /** @see {_at_link
ResponseStateManager#isPostback(javax.faces.context.FacesContext)} */
+ @Override
     public boolean isPostback(FacesContext context) {
 
         return context.getExternalContext().getRequestParameterMap().
               containsKey(ResponseStateManager.VIEW_STATE_PARAM);
 
- }
+ }
 
 
- /**
- * @see {_at_link
ResponseStateManager#getTreeStructureToRestore(javax.faces.context.FacesContext,
String)}
- */
+ /** @see {_at_link
ResponseStateManager#getTreeStructureToRestore(javax.faces.context.FacesContext,String)}
*/
     @Override
     @SuppressWarnings("Deprecation")
     public Object getTreeStructureToRestore(FacesContext context,
@@ -121,44 +126,40 @@
         StateManager stateManager = Util.getStateManager(context);
 
         String viewString = getStateParam(context);
- Object structure;
+
         if (viewString == null) {
             return null;
         }
 
         if (stateManager.isSavingStateInClient(context)) {
- Object state;
- ByteArrayInputStream bis;
- GZIPInputStream gis = null;
- ObjectInputStream ois;
- boolean compress = isCompressStateSet(context);
-
- try {
- byte[] bytes = byteArrayGuard.decrypt(
- (Base64.decode(viewString.getBytes())));
- bis = new ByteArrayInputStream(bytes);
- if (isCompressStateSet(context)) {
- if (LOGGER.isLoggable(Level.FINE)) {
- LOGGER.fine("Deflating state before restoring..");
- }
- gis = new GZIPInputStream(bis);
- ois = new ApplicationObjectInputStream(gis);
+
+
+ ObjectInputStream ois = null;
+
+ try {
+ InputStream bis;
+ if (guard != null) {
+ bis = new CipherInputStream(
+ new Base64InputStream(viewString),
+ guard.getDecryptionCipher());
                 } else {
- ois = new ApplicationObjectInputStream(bis);
+ bis = new Base64InputStream(viewString);
                 }
- structure = ois.readObject();
- state = ois.readObject();
-
- bis.close();
- if (compress) {
- if (gis != null) {
- gis.close();
- }
+
+ if (compressState)
{
+ ois = new ApplicationObjectInputStream(
+ new GZIPInputStream(bis));
+ } else {
+ ois = new ApplicationObjectInputStream(bis);
                 }
+ Object structure = ois.readObject();
+ Object state = ois.readObject();
+
                 ois.close();
-
+
                 storeStateInRequest(context, state);
-
+ return structure;
+
             } catch (java.io.OptionalDataException ode) {
                 LOGGER.log(Level.SEVERE, ode.getMessage(), ode);
                 throw new FacesException(ode);
@@ -168,67 +169,81 @@
             } catch (java.io.IOException iox) {
                 LOGGER.log(Level.SEVERE, iox.getMessage(), iox);
                 throw new FacesException(iox);
+ } finally {
+ if (ois != null) {
+ try {
+ ois.close();
+ } catch (IOException ioe) {
+ // ignore
+ }
+ }
             }
         } else {
- structure = viewString;
- }
- return structure;
-
+ return viewString;
+ }
     }
-
 
- /**
- * @see {_at_link
ResponseStateManager#writeState(javax.faces.context.FacesContext,
javax.faces.application.StateManager.SerializedView)}
- */
+
+ /** @see {_at_link
ResponseStateManager#writeState(javax.faces.context.FacesContext,javax.faces.application.StateManager.SerializedView)}
*/
     @Override
     @SuppressWarnings("Deprecation")
     public void writeState(FacesContext context, SerializedView view)
- throws IOException {
+ throws IOException {
 
         StateManager stateManager = Util.getStateManager(context);
         ResponseWriter writer = context.getResponseWriter();
 
- writer.startElement("input", context.getViewRoot());
- writer.writeAttribute("type", "hidden", null);
- writer.writeAttribute("name",
-
javax.faces.render.ResponseStateManager.VIEW_STATE_PARAM,
- null);
- writer.writeAttribute("id",
-
javax.faces.render.ResponseStateManager.VIEW_STATE_PARAM,
- null);
-
+ writer.write(STATE_FIELD_START);
 
         if (stateManager.isSavingStateInClient(context)) {
- GZIPOutputStream zos = null;
- ObjectOutputStream oos;
- boolean compress = isCompressStateSet(context);
+ ObjectOutputStream oos = null;
+ try {
 
- ByteArrayOutputStream bos = new ByteArrayOutputStream();
- if (compress) {
+ Base64OutputStreamWriter bos =
+ new Base64OutputStreamWriter(csBuffSize,
+ writer);
+ OutputStream base;
+ if (guard != null) {
+ base = new CipherOutputStream(bos,
+
guard.getEncryptionCipher());
+ } else {
+ base = bos;
+ }
+ if (compressState) {
+ oos = new ObjectOutputStream(
+ new GZIPOutputStream(base));
+ } else {
+ oos = new ObjectOutputStream(base);
+ }
+
+ oos.writeObject(view.getStructure());
+ oos.writeObject(view.getState());
+ oos.flush();
+ oos.close();
+
+ // flush everything to the underlying writer
+ bos.finish();
+
                 if (LOGGER.isLoggable(Level.FINE)) {
- LOGGER.fine("Compressing state before saving..");
+ LOGGER.fine("Client State: total number of characters"
+ + " written: " +
bos.getTotalCharsWritten());
                 }
- zos = new GZIPOutputStream(bos);
- oos = new ObjectOutputStream(zos);
- } else {
- oos = new ObjectOutputStream(bos);
- }
- oos.writeObject(view.getStructure());
- oos.writeObject(view.getState());
- oos.close();
- if (compress) {
- zos.close();
+ } finally {
+ if (oos != null) {
+ try {
+ oos.close();
+ } catch (IOException ioe) {
+ // ignore
+ }
+ }
+
             }
- byte[] securedata = byteArrayGuard.encrypt(bos.toByteArray());
- bos.close();
- String valueToWrite = (new String(Base64.encode(securedata),
- "ISO-8859-1"));
- writer.writeAttribute("value",
- valueToWrite, null);
+
         } else {
- writer.writeAttribute("value", view.getStructure(), null);
+ writer.write(view.getStructure().toString());
         }
- writer.endElement("input");
+
+ writer.write(STATE_FIELD_END);
 
         writeRenderKitIdField(context, writer);
 
@@ -238,19 +253,20 @@
     /**
      * <p>Store the state for this request into a temporary attribute
      * within the same request.</p>
+ *
      * @param context the <code>FacesContext</code> of the current request
- * @param state the view state
+ * @param state the view state
      */
- private void storeStateInRequest(FacesContext context, Object state) {
+ private void storeStateInRequest(FacesContext context, Object state) {
 
         // store the state object temporarily in request scope
         // until it is processed by getComponentStateToRestore
         // which resets it.
         context.getExternalContext().getRequestMap()
               .put(FACES_VIEW_STATE, state);
-
+
     }
-
+
 
     /**
      * <p>Write a hidden field if the default render kit ID is not
@@ -264,8 +280,8 @@
     private void writeRenderKitIdField(FacesContext context,
                                        ResponseWriter writer)
           throws IOException {
- String result =
context.getApplication().getDefaultRenderKitId();
- if (result != null &&
+ String result = context.getApplication().getDefaultRenderKitId();
+ if (result != null &&
             !RenderKitFactory.HTML_BASIC_RENDER_KIT.equals(result)) {
             writer.startElement("input", context.getViewRoot());
             writer.writeAttribute("type", "hidden", "type");
@@ -281,35 +297,69 @@
 
     /**
      * <p>Get our view state from this request</p>
+ *
      * @param context the <code>FacesContext</code> for the current request
+ *
      * @return the view state from this request
      */
- private String getStateParam(FacesContext context) {
+ private String getStateParam(FacesContext context) {
 
         return context.getExternalContext().getRequestParameterMap().get(
- ResponseStateManager.VIEW_STATE_PARAM);
+ ResponseStateManager.VIEW_STATE_PARAM);
     }
 
 
     /**
- * <p>Determines if client state should or should not be
- * compressed.</p>
- * @param context the <code>FacesContext</code> for the current request
- * @return <code>true</code> if the state should be compressed before
- * writing to the response, otherwise <code>false</code>
+ * <p>Perform the necessary intialization to make this
+ * class work.</p>
      */
- private boolean isCompressStateSet(FacesContext context) {
-
- if (null != compressState) {
- return compressState;
+ private void init() {
+
+ WebConfiguration webConfig = WebConfiguration.getInstance();
+ assert(webConfig != null);
+
+ String pass = webConfig.getEnvironmentEntry(
+ WebEnvironmentEntry.ClientStateSavingPassword);
+ if (pass != null) {
+ guard = new ByteArrayGuard(pass);
+ }
+ compressState = webConfig.getBooleanContextInitParameter(
+
BooleanWebContextInitParameter.CompressViewState);
+ String size = webConfig.getContextInitParameter(
+
WebContextInitParameter.ClientStateWriteBufferSize);
+ String defaultSize =
+
WebContextInitParameter.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[] {
+
WebContextInitParameter.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[] {
+
WebContextInitParameter.ClientStateWriteBufferSize.getQualifiedName(),
+ size,
+ defaultSize});
+ }
+ csBuffSize = Integer.parseInt(defaultSize);
         }
- compressState = WebConfiguration
- .getInstance(context.getExternalContext())
-
.getBooleanContextInitParameter(BooleanWebContextInitParameter.CompressViewState);
-
- return compressState;
-
     }
 
+
 } // end of class ResponseStateManagerImpl
-
Index:
systest/src/com/sun/faces/systest/render/CustomResponseStateManagerImpl.java
===================================================================
RCS file:
/cvs/javaserverfaces-sources/jsf-ri/systest/src/com/sun/faces/systest/render/CustomResponseStateManagerImpl.java,v
retrieving revision 1.9
diff -u -r1.9 CustomResponseStateManagerImpl.java
---
systest/src/com/sun/faces/systest/render/CustomResponseStateManagerImpl.java
 5 Apr 2006 17:53:44 -0000 1.9
+++
systest/src/com/sun/faces/systest/render/CustomResponseStateManagerImpl.java
 31 May 2006 18:25:41 -0000
@@ -30,14 +30,8 @@
 
 package com.sun.faces.systest.render;
 
-import com.sun.faces.renderkit.ByteArrayGuard;
-import com.sun.faces.util.Base64;
-import com.sun.faces.util.Util;
-import com.sun.org.apache.commons.logging.Log;
-import com.sun.org.apache.commons.logging.LogFactory;
-
-import javax.faces.application.StateManager.SerializedView;
 import javax.faces.application.StateManager;
+import javax.faces.application.StateManager.SerializedView;
 import javax.faces.context.FacesContext;
 import javax.faces.render.ResponseStateManager;
 
@@ -46,10 +40,13 @@
 import java.io.IOException;
 import java.io.ObjectInputStream;
 import java.io.ObjectOutputStream;
-
+import java.util.Map;
 import java.util.zip.GZIPInputStream;
 import java.util.zip.GZIPOutputStream;
-import java.util.Map;
+
+import com.sun.faces.util.Util;
+import com.sun.org.apache.commons.logging.Log;
+import com.sun.org.apache.commons.logging.LogFactory;
 
 
 /**
@@ -76,7 +73,7 @@
     // Instance Variables
     //
     private Boolean compressStateSet = null;
- private ByteArrayGuard byteArrayGuard = null;
+
     
     //
     // Ivars used during actual client lifetime
@@ -90,8 +87,7 @@
     //
 
     public CustomResponseStateManagerImpl() {
- super();
- byteArrayGuard = ByteArrayGuard.newInstance();
+ super();
     }
 
 
@@ -151,8 +147,7 @@
         boolean compress = isCompressStateSet(context);
        
         try {
- byte[] bytes = byteArrayGuard.decrypt(
- (Base64.decode(viewString.getBytes())));
+ byte[] bytes = Base64.decode(viewString.getBytes());
         bis = new ByteArrayInputStream(bytes);
         if (isCompressStateSet(context)) {
             if (log.isDebugEnabled()) {
@@ -230,8 +225,7 @@
         if (compress) {
         zos.close();
         }
- byte[] securedata = byteArrayGuard.encrypt(
- bos.toByteArray());
+ byte[] securedata = bos.toByteArray();
         bos.close();
         
         hiddenField = " <input type=\"hidden\" name=\""


SECTION: New Files
----------------------------
SEE ATTACHMENTS