/**
* License Agreement.
*
* Rich Faces - Natural Ajax for Java Server Faces (JSF)
*
* Copyright (C) 2007 Exadel, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License version 2.1 as published by the Free Software Foundation.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package org.ajax4jsf.application;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.ObjectStreamClass;
import java.io.StringWriter;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.faces.FacesException;
import javax.faces.FactoryFinder;
import javax.faces.application.StateManager;
import javax.faces.application.StateManager.SerializedView;
import javax.faces.component.UIComponentBase;
import javax.faces.component.UIViewRoot;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import javax.faces.render.RenderKit;
import javax.faces.render.RenderKitFactory;
import javax.faces.render.ResponseStateManager;
import org.ajax4jsf.context.AjaxContext;
import org.ajax4jsf.context.ContextInitParameters;
import org.ajax4jsf.model.KeepAlive;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* @author shura
*
*/
public class AjaxStateManager extends StateManager {
public static final String CAPTURED_VIEW_STATE = "org.ajax4jsf.captured_view_state";
private final class SeamStateManagerWrapper extends StateManager {
protected Object getComponentStateToSave(FacesContext arg0) {
// do nothing
return null;
}
protected Object getTreeStructureToSave(FacesContext arg0) {
// do nothing
return null;
}
protected void restoreComponentState(FacesContext arg0,
UIViewRoot arg1, String arg2) {
// do nothing
}
protected UIViewRoot restoreTreeStructure(FacesContext arg0,
String arg1, String arg2) {
// do nothing
return null;
}
public UIViewRoot restoreView(FacesContext arg0, String arg1,
String arg2) {
// do nothing
return null;
}
@SuppressWarnings("deprecation")
public SerializedView saveSerializedView(FacesContext context) {
// delegate to enclosed class method.
Object[] viewState = buildViewState(context);
return new SerializedView(viewState[0],viewState[1]);
}
@Override
public Object saveView(FacesContext context) {
// TODO Auto-generated method stub
return buildViewState(context);
}
@SuppressWarnings("deprecation")
public void writeState(FacesContext arg0, SerializedView arg1)
throws IOException {
// do nothing
}
}
private static final Class<StateManager> STATE_MANAGER_ARGUMENTS = StateManager.class;
public static final int DEFAULT_NUMBER_OF_VIEWS = 16;
public static final String AJAX_VIEW_SEQUENCE = AjaxStateManager.class.getName()
+ ".AJAX_VIEW_SEQUENCE";
public static final String VIEW_SEQUENCE = AjaxStateManager.class.getName()
+ ".VIEW_SEQUENCE";
private final StateManager parent;
private StateManager seamStateManager;
private final ComponentsLoader componentLoader;
private static final Log _log = LogFactory.getLog(AjaxStateManager.class);
public static final String VIEW_SEQUENCE_ATTRIBUTE = AjaxStateManager.class
.getName()
+ ".view_sequence";
/**
* @param parent
*/
public AjaxStateManager(StateManager parent) {
super();
this.parent = parent;
componentLoader = new ComponentsLoaderImpl();
// HACK - Seam perform significant operations before save tree state.
// Try to create it instance by reflection,
// to call in real state saving operations.
ClassLoader classLoader = Thread.currentThread()
.getContextClassLoader();
if (null == classLoader) {
classLoader = AjaxStateManager.class.getClassLoader();
}
try {
Class<? extends StateManager> seamStateManagerClass = classLoader
.loadClass("org.jboss.seam.jsf.SeamStateManager")
.asSubclass(StateManager.class);
Constructor<? extends StateManager> constructor = seamStateManagerClass
.getConstructor(STATE_MANAGER_ARGUMENTS);
seamStateManager = constructor
.newInstance(new Object[] { new SeamStateManagerWrapper() });
if (_log.isDebugEnabled()) {
_log.debug("Create instance of the SeamStateManager");
}
} catch (Exception e) {
seamStateManager = null;
if (_log.isDebugEnabled()) {
_log.debug("SeamStateManager is not present");
}
}
}
/*
* (non-Javadoc)
*
* @see javax.faces.application.StateManager#getComponentStateToSave(javax.faces.context.FacesContext)
*/
protected Object getComponentStateToSave(FacesContext context) {
Object treeState = context.getViewRoot().processSaveState(context);
Object state[] = { treeState, getAdditionalState(context) };
return state;
}
/*
* (non-Javadoc)
*
* @see javax.faces.application.StateManager#getTreeStructureToSave(javax.faces.context.FacesContext)
*/
protected Object getTreeStructureToSave(FacesContext context) {
TreeStructureNode treeStructure = new TreeStructureNode();
treeStructure.apply(context, context.getViewRoot(),
new HashSet<String>());
return treeStructure;
}
/*
* (non-Javadoc)
*
* @see javax.faces.application.StateManager#restoreComponentState(javax.faces.context.FacesContext,
* javax.faces.component.UIViewRoot, java.lang.String)
*/
protected void restoreComponentState(FacesContext context,
UIViewRoot viewRoot, String renderKitId) {
throw new UnsupportedOperationException();
}
/*
* (non-Javadoc)
*
* @see javax.faces.application.StateManager#restoreTreeStructure(javax.faces.context.FacesContext,
* java.lang.String, java.lang.String)
*/
protected UIViewRoot restoreTreeStructure(FacesContext context,
String viewId, String renderKitId) {
throw new UnsupportedOperationException();
}
/*
* (non-Javadoc)
*
* @see javax.faces.application.StateManager#writeState(javax.faces.context.FacesContext,
* javax.faces.application.StateManager.SerializedView)
*/
public void writeState(FacesContext context, Object state)
throws IOException {
RenderKit renderKit = getRenderKit(context);
ResponseStateManager responseStateManager = renderKit
.getResponseStateManager();
Object[] stateArray = getStateArray( state );
if(null == stateArray[0] && null == stateArray[1]){
// Myfaces https://issues.apache.org/jira/browse/MYFACES-1753 hack.
stateArray=new Object[]{getLogicalViewId(context),null};
}
writeState(context, responseStateManager, stateArray);
if (_log.isDebugEnabled()) {
_log.debug("Write view state to the response");
}
}
/*
* (non-Javadoc)
*
* @see javax.faces.application.StateManager#writeState(javax.faces.context.FacesContext,
* javax.faces.application.StateManager.SerializedView)
*/
@SuppressWarnings("deprecation")
public void writeState(FacesContext context, SerializedView state)
throws IOException {
RenderKit renderKit = getRenderKit(context);
ResponseStateManager responseStateManager = renderKit
.getResponseStateManager();
Object[] stateArray;
if(null == state.getState() && null == state.getStructure()){
// MyFaces https://issues.apache.org/jira/browse/MYFACES-1753 hack
stateArray = new Object[]{getLogicalViewId(context),null};
} else {
stateArray = new Object[] {
state.getStructure(),state.getState() };
}
writeState(context, responseStateManager, stateArray);
if (_log.isDebugEnabled()) {
_log.debug("Write view state to the response");
}
}
/**
* @param context
* @param state
* @param responseStateManager
* @throws IOException
* @throws FacesException
*/
private Object[] getStateArray(Object state) throws IOException,
FacesException {
if (null != state && state.getClass().isArray()
&& state.getClass().getComponentType().equals(Object.class)) {
Object stateArray[] = (Object[]) state;
if (2 == stateArray.length) {
return stateArray;
} else {
throw new FacesException("Unexpected length of the state object array "+stateArray.length);
}
} else {
throw new FacesException("Unexpected type of the state "+state.getClass().getName());
}
}
private void writeState(FacesContext context,
ResponseStateManager responseStateManager, Object[] stateArray)
throws IOException {
// Capture writed state into string.
ResponseWriter originalWriter = context.getResponseWriter();
StringWriter buff = new StringWriter(128);
try {
ResponseWriter stateResponseWriter = originalWriter
.cloneWithWriter(buff);
context.setResponseWriter(stateResponseWriter);
responseStateManager.writeState(context, stateArray);
stateResponseWriter.flush();
String stateString = buff.toString();
originalWriter.write(stateString);
String stateValue = getStateValue(stateString);
context.getExternalContext().getRequestMap().put(CAPTURED_VIEW_STATE, stateValue);
if (null != stateValue) {
} else {
}
} finally {
context.setResponseWriter(originalWriter);
}
}
private static final Pattern PATTERN = Pattern.compile(".*<input.*(?:\\svalue=[\"\'](.*)[\"\']\\s).*name=[\"']"+ResponseStateManager.VIEW_STATE_PARAM+"[\"'].*>");
private static final Pattern PATTERN2 = Pattern.compile(".*<input .*name=[\"']"+ResponseStateManager.VIEW_STATE_PARAM+"[\"'].*(?:\\svalue=[\"\'](.*)[\"\']\\s).*>");
/**
* Parse content of the writed viewState hidden input field for a state value.
* @param input
* @return
*/
private String getStateValue(String input) {
Matcher matcher = PATTERN.matcher(input);
if(!matcher.matches()){
matcher = PATTERN2.matcher(input);
if(!matcher.matches()){
return null;
}
}
return matcher.group(1);
}
private static final Object handleRestoreState(FacesContext context, Object state) {
if (ContextInitParameters.isSerializeServerState(context)) {
ObjectInputStream ois = null;
try {
ois = new ObjectInputStream(new ByteArrayInputStream((byte[]) state)) {
@Override
protected Class<?> resolveClass(ObjectStreamClass desc)
throws IOException, ClassNotFoundException {
return Class.forName(desc.getName(), true,
Thread.currentThread().getContextClassLoader());
}
};
return ois.readObject();
} catch (Exception e) {
throw new FacesException(e);
} finally {
if (ois != null) {
try {
ois.close();
} catch (IOException ignored) { }
}
}
} else {
return state;
}
}
private static final Object handleSaveState(FacesContext context, Object state) {
if (ContextInitParameters.isSerializeServerState(context)) {
ByteArrayOutputStream baos = new ByteArrayOutputStream(1024);
ObjectOutputStream oas = null;
try {
oas = new ObjectOutputStream(baos);
oas.writeObject(state);
oas.flush();
} catch (Exception e) {
throw new FacesException(e);
} finally {
if (oas != null) {
try {
oas.close();
} catch (IOException ignored) { }
}
}
return baos.toByteArray();
} else {
return state;
}
}
/*
* (non-Javadoc)
*
* @see javax.faces.application.StateManager#restoreView(javax.faces.context.FacesContext,
* java.lang.String, java.lang.String)
*/
public UIViewRoot restoreView(FacesContext context, String viewId,
String renderKitId) {
UIViewRoot viewRoot = null;
ResponseStateManager responseStateManager = getRenderKit(context,
renderKitId).getResponseStateManager();
TreeStructureNode treeStructure = null;
Object[] state = null;
Object[] serializedView = null;
if (isSavingStateInClient(context)) {
serializedView = (Object[]) responseStateManager.getState(context,
viewId);
if (null != serializedView) {
treeStructure = (TreeStructureNode) serializedView[0];
state = (Object[]) serializedView[1];
}
} else {
serializedView = restoreStateFromSession(context, viewId,
renderKitId);
if (null != serializedView) {
treeStructure = (TreeStructureNode) serializedView[0];
state = (Object[]) handleRestoreState(context, serializedView[1]);
}
}
if (null != treeStructure) {
viewRoot = (UIViewRoot) treeStructure.restore(componentLoader);
if (null != viewRoot && null != state) {
viewRoot.processRestoreState(context, state[0]);
restoreAdditionalState(context, state[1]);
}
}
return viewRoot;
}
@SuppressWarnings("deprecation")
public SerializedView saveSerializedView(FacesContext context) {
Object[] stateViewArray;
if (null == seamStateManager) {
stateViewArray = buildViewState(context);
} else {
// Delegate save method to seam State Manager.
stateViewArray=(Object[]) seamStateManager.saveView(context);
}
return new SerializedView(stateViewArray[0],stateViewArray[1]);
}
@Override
public Object saveView(FacesContext context) {
if (null == seamStateManager) {
return buildViewState(context);
} else {
// Delegate save method to seam State Manager.
return seamStateManager.saveView(context);
}
}
/**
* @param context
* @return
* @see javax.faces.application.StateManager#isSavingStateInClient(javax.faces.context.FacesContext)
*/
public boolean isSavingStateInClient(FacesContext context) {
return parent.isSavingStateInClient(context);
}
protected Object[] restoreStateFromSession(FacesContext context,
String viewId, String renderKitId) {
String id = restoreLogicalViewId(context, viewId, renderKitId);
StateHolder stateHolder = getStateHolder(context);
Object[] restoredState = stateHolder.getState(viewId, id);
return restoredState;
}
/**
* @param context
* @return
*/
protected Object[] buildViewState(FacesContext context) {
Object[] viewStateArray = null;
UIViewRoot viewRoot = context.getViewRoot();
if (null != viewRoot && !viewRoot.isTransient()) {
TreeStructureNode treeStructure = (TreeStructureNode) getTreeStructureToSave(context);
Object state = getComponentStateToSave(context);
if (isSavingStateInClient(context)) {
viewStateArray = new Object[]{treeStructure, state};
} else {
viewStateArray = saveStateInSession(context, treeStructure,
handleSaveState(context, state));
}
}
return viewStateArray;
}
/**
* @param context
* @param treeStructure
* @param state
* @return
*/
protected Object[] saveStateInSession(FacesContext context,
Object treeStructure, Object state) {
Object[] serializedView;
UIViewRoot viewRoot = context.getViewRoot();
StateHolder stateHolder = getStateHolder(context);
String id = getLogicalViewId(context);
stateHolder.saveState(viewRoot.getViewId(), id, new Object[] {
treeStructure, state });
serializedView = new Object[]{id, null};
return serializedView;
}
/**
* @param context
* @return
*/
protected StateHolder getStateHolder(FacesContext context) {
return AjaxStateHolder.getInstance(context);
}
protected Object getAdditionalState(FacesContext context) {
Map<String, Object> keepAliveBeans = new HashMap<String, Object>();
Map<String, Object> requestMap = context.getExternalContext()
.getRequestMap();
// Save all objects form request map wich marked by @KeepAlive
// annotations
for (Entry<String, Object> requestEntry : requestMap.entrySet()) {
Object bean = requestEntry.getValue();
// check value for a NULL -
// http://jira.jboss.com/jira/browse/RF-3576
if (null != bean
&& bean.getClass().isAnnotationPresent(KeepAlive.class)) {
keepAliveBeans.put(requestEntry.getKey(), bean);
}
}
if (keepAliveBeans.size() > 0) {
return UIComponentBase.saveAttachedState(context, keepAliveBeans);
} else {
return null;
}
}
@SuppressWarnings("unchecked")
protected void restoreAdditionalState(FacesContext context, Object state) {
if (null != state) {
boolean isAjax = AjaxContext.getCurrentInstance(context).isAjaxRequest();
// Append all saved beans to the request map.
Map beansMap = (Map) UIComponentBase.restoreAttachedState(context,
state);
Map<String, Object> requestMap = context.getExternalContext()
.getRequestMap();
for (Object key : beansMap.keySet()) {
Object bean = beansMap.get(key);
if (bean != null) {
KeepAlive annotation = bean.getClass().getAnnotation(KeepAlive.class);
if (annotation != null) {
if (!isAjax && annotation.ajaxOnly()) {
//skip ajax-only beans for non-ajax requests
continue;
}
}
}
requestMap.put((String) key, bean);
}
}
}
/**
* Restore logical view id from request.
*
* @param context
* @param viewId
* @param renderKitId
* @return
*/
@SuppressWarnings("deprecation")
protected String restoreLogicalViewId(FacesContext context, String viewId,
String renderKitId) {
String id = (String) getRenderKit(context, renderKitId)
.getResponseStateManager().getTreeStructureToRestore(context,
viewId);
if (null != id) {
context.getExternalContext().getRequestMap().put(AJAX_VIEW_SEQUENCE, id);
}
return id;
}
/**
* Return logical Id for current request view state. For a faces requests,
* generate sequence numbers. For a ajax request, attempt to re-use id from
* request submit.
*
* @param context
* @return
*/
protected String getLogicalViewId(FacesContext context) {
AjaxContext ajaxContext = AjaxContext.getCurrentInstance(context);
ExternalContext externalContext = context.getExternalContext();
Object id=null;
Map<String, Object> requestMap = externalContext.getRequestMap();
id = requestMap.get(ajaxContext.isAjaxRequest()?AJAX_VIEW_SEQUENCE:VIEW_SEQUENCE);
if (null != id) {
return id.toString();
}
// Store sequence in session, to avoyd claster configuration problem
// see https://javaserverfaces.dev.java.net/issues/show_bug.cgi?id=662
Object session = externalContext.getSession(true);
int viewSequence;
synchronized (session) {
Map<String, Object> sessionMap = externalContext.getSessionMap();
Integer sequence = (Integer) sessionMap
.get(VIEW_SEQUENCE_ATTRIBUTE);
if (null != sequence) {
viewSequence = sequence.intValue();
} else {
viewSequence = 0;
}
if (viewSequence++ == Character.MAX_VALUE) {
viewSequence = 0;
}
sessionMap.put(VIEW_SEQUENCE_ATTRIBUTE, new Integer(viewSequence));
}
String logicalViewId = UIViewRoot.UNIQUE_ID_PREFIX + ((int) viewSequence);
// Store new viewId in the request parameters, to avoid re-increments in the same request.
requestMap.put(VIEW_SEQUENCE,logicalViewId);
return logicalViewId;
}
protected RenderKit getRenderKit(FacesContext context) {
String renderKitId = null;
UIViewRoot viewRoot = context.getViewRoot();
if (null != viewRoot) {
renderKitId = viewRoot.getRenderKitId();
}
if (null == renderKitId) {
renderKitId = context.getApplication().getViewHandler()
.calculateRenderKitId(context);
}
return getRenderKit(context, renderKitId);
}
protected RenderKit getRenderKit(FacesContext context, String renderKitId) {
RenderKit renderKit = context.getRenderKit();
if (null == renderKit) {
RenderKitFactory factory = (RenderKitFactory) FactoryFinder
.getFactory(FactoryFinder.RENDER_KIT_FACTORY);
renderKit = factory.getRenderKit(context, renderKitId);
}
return renderKit;
}
}