blob: ea60461cb47dc8b85858cf6b68b544472cf6d715 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.myfaces.trinidadinternal.renderkit.core;
import java.io.IOException;
import java.util.Map;
import javax.faces.FacesException;
import javax.faces.application.StateManager;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import javax.faces.render.ResponseStateManager;
import org.apache.myfaces.trinidad.logging.TrinidadLogger;
import org.apache.myfaces.trinidadinternal.application.StateManagerImpl;
import org.apache.myfaces.trinidadinternal.util.JsfUtils;
import org.apache.myfaces.trinidadinternal.util.StateUtils;
/**
* ResponseStateManager implementation for the Core RenderKit.
* <p>
*/
public class CoreResponseStateManager extends ResponseStateManager
{
@Override
public Object getState(FacesContext context, String viewId)
{
// TODO see doc in StateManagerImpl.restoreView
// (search for StateManagerImpl.RESPONSE_STATE_MANAGER_STATE_KEY) to see doc
// about what's going on here
Object state = context.getExternalContext().getRequestMap().get(
StateManagerImpl.RESPONSE_STATE_MANAGER_STATE_KEY);
if (state != null)
return state;
else
return super.getState(context, viewId);
}
/**
* Name of the form field that encodes the UI state.
*/
static public final String FORM_FIELD_NAME = "org.apache.myfaces.trinidad.faces.FORM";
/**
* Write the state into the page.
* @todo Stream the resulting state into the page's content
* instead of buffering it.
* @todo Don't directly write out hidden input field; use an abstraction
*/
@Override
public void writeState(
FacesContext context,
StateManager.SerializedView serializedView) throws IOException
{
ResponseWriter rw = context.getResponseWriter();
rw.startElement("input", null);
rw.writeAttribute("type", "hidden", null);
rw.writeAttribute("name", VIEW_STATE_PARAM, null);
if (JsfUtils.IS_JSF_2_2)
{
rw.writeAttribute("id", StateUtils.getViewStateId(context), null);
}
String s = encodeSerializedViewAsString(context, serializedView);
rw.writeAttribute("value", s, null);
rw.endElement("input");
}
/**
* A request is a postback if it contains the state parameter.
*/
@Override
public boolean isPostback(FacesContext context)
{
Map requestParams = context.getExternalContext().getRequestParameterMap();
return requestParams.containsKey(VIEW_STATE_PARAM);
}
protected String encodeSerializedViewAsString(FacesContext context,
StateManager.SerializedView serializedView) throws IOException
{
if ((serializedView.getState() == null) &&
(serializedView.getStructure() instanceof String))
return _TOKEN_PREFIX + StateUtils.construct(serializedView.getStructure(), context.getExternalContext());
return StateUtils.construct(
new Object[]{serializedView.getStructure(),serializedView.getState()}, context.getExternalContext());
/*
StringWriter sw = new StringWriter();
BufferedWriter bw = new BufferedWriter(sw);
Base64OutputStream b64_out = new Base64OutputStream(bw);
GZIPOutputStream zip = new GZIPOutputStream(b64_out, _BUFFER_SIZE);
ObjectOutput output = new ObjectOutputStream(zip);
output.writeObject(serializedView.getStructure());
output.writeObject(serializedView.getState());
// this will flush the ObjectOutputStream, and close the GZIP stream, which
// will call finish() on the GZIP stream, and then close the B64 stream,
// which will cause it to write out the proper padding, and then close
// the BufferedWriter which will also cause it to flush:
output.close();
String retVal = sw.toString();
assert(!retVal.startsWith(_TOKEN_PREFIX));
return retVal;*/
}
@Override
public Object getTreeStructureToRestore(FacesContext context,
String viewId)
{
Object[] view = _restoreSerializedView(context);
if (view == null)
return null;
return view[0];
}
@Override
public Object getComponentStateToRestore(FacesContext context)
{
Object[] view = _restoreSerializedView(context);
if (view == null)
return null;
return view[1];
}
@Override
public String getViewState(FacesContext context, Object state)
{
StateManager.SerializedView serializedView = _getSerializedView(context, state);
try
{
return encodeSerializedViewAsString(context, serializedView);
}
catch (IOException e)
{
throw new FacesException();
}
}
private StateManager.SerializedView _getSerializedView(FacesContext context, Object state)
{
StateManager.SerializedView view;
if (state instanceof StateManager.SerializedView)
{
view = (StateManager.SerializedView) state;
}
else
{
if (state instanceof Object[])
{
Object[] stateArray = (Object[]) state;
// in theory the state should be a black box, but the RI makes assumptions
// that the state is an array of length 2
if (stateArray.length == 2)
{
StateManager stateManager =
context.getApplication().getStateManager();
view =
stateManager.new SerializedView(stateArray[0], stateArray[1]);
}
else
{
throw new IllegalArgumentException(_LOG.getMessage("UNEXPECTED_STATE"));
}
}
else
{
throw new IllegalArgumentException(_LOG.getMessage("UNEXPECTED_STATE"));
}
}
return view;
}
/**
* Returns the State token if a valid state token is available in the request, or null if not.
* @param external
* @return
*/
public static String getStateToken(ExternalContext external)
{
return _tokenStringFromStateString(_getStateString(external));
}
/**
* Returns the State String for the request (either token or client state value)
* @param external
* @return
*/
private static String _getStateString(ExternalContext external)
{
return external.getRequestParameterMap().get(VIEW_STATE_PARAM);
}
/**
* Give a client state String, return the tokenString for it if present, or null
*/
private static String _tokenStringFromStateString(String stateString)
{
if (stateString != null)
{
// See if we've got a token; that'll be the case if we're
// prefixed by _TOKEN_PREFIX
if (stateString.startsWith(_TOKEN_PREFIX))
{
return stateString.substring(_TOKEN_PREFIX.length());
}
}
return null;
}
/**
* Restore the serialized view from the incoming request.
* @todo ensure that the caching never gets confused by two
* different views being reconstituted in the same request?
*/
@SuppressWarnings("unchecked")
private Object[] _restoreSerializedView(
FacesContext context)
{
ExternalContext external = context.getExternalContext();
Map<String, Object> requestMap = external.getRequestMap();
Object[] view = (Object[]) requestMap.get(_CACHED_SERIALIZED_VIEW);
if (view == null)
{
String stateString = _getStateString(external);
String tokenString = _tokenStringFromStateString(stateString);
if (tokenString != null)
{
view = new Object[]{StateUtils.reconstruct(tokenString, context.getExternalContext()), null};
}
// Nope, let's look for a regular state field
else
{
if (stateString != null)
{
view = (Object[]) StateUtils.reconstruct(stateString, context.getExternalContext());
/*
StringReader sr = new StringReader(stateString);
BufferedReader br = new BufferedReader(sr);
Base64InputStream b64_in = new Base64InputStream(br);
try
{
ObjectInputStream ois;
ois = new ObjectInputStreamResolveClass( new GZIPInputStream( b64_in, _BUFFER_SIZE ));
Object structure = ois.readObject();
Object state = ois.readObject();
ois.close();
view = new Object[]{structure, state};
}
catch (OptionalDataException ode)
{
_LOG.severe(ode);
}
catch (ClassNotFoundException cnfe)
{
_LOG.severe(cnfe);
}
catch (IOException ioe)
{
_LOG.severe(ioe);
}
*/
}
}
if (view != null)
requestMap.put(_CACHED_SERIALIZED_VIEW, view);
}
return view;
}
/* Test code for dumping out the page's state
static private void _dump(Object o)
{
System.out.println("DUMPING STATE");
_dump(o, 0);
}
static private void _dump(Object o, int depth)
{
if (o instanceof Object[])
{
Object[] array = (Object[]) o;
_spaces(depth);
System.out.println("array of length " + array.length);
for (int i = 0; i < array.length; i++)
_dump(array[i], depth + 1);
}
else
{
_spaces(depth);
System.out.println(o);
}
}
static private void _spaces(int count)
{
int i = 0;
for (; i + 5 < count; i += 5)
System.out.print(" ");
for (; i < count; i++)
System.out.print(" ");
}
*/
static private final int _BUFFER_SIZE = 2048;
static private final String _CACHED_SERIALIZED_VIEW =
"org.apache.myfaces.trinidadinternal.renderkit.CACHED_SERIALIZED_VIEW";
// Exclamation marks are not legit Base64 characters; only
// A-Z, a-z, 0-9, +, /, and = can ever show up.
static private final String _TOKEN_PREFIX = "!";
static private final TrinidadLogger _LOG = TrinidadLogger.createTrinidadLogger(CoreResponseStateManager.class);
}