/**
* 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.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;
import javax.faces.FactoryFinder;
import javax.faces.application.StateManager;
import javax.faces.component.UIComponent;
import javax.faces.component.UIViewRoot;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.faces.render.RenderKit;
import javax.faces.render.RenderKitFactory;
import javax.faces.render.ResponseStateManager;
import org.ajax4jsf.context.AjaxContext;
import org.ajax4jsf.event.AjaxPhaseListener;
import org.apache.commons.collections.map.LRUMap;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* @author shura
*
*/
public class AjaxStateManager extends StateManager {
private static final Class[] STATE_MANAGER_ARGUMENTS = new Class[] { StateManager.class };
protected static final int DEFAULT_NUMBER_OF_VIEWS = 16;
private static final String VIEW_STATES_MAP = AjaxStateManager.class
.getName()
+ ".VIEW_STATES_MAP";
private static final Object VIEW_SEQUENCE = AjaxStateManager.class
.getName()
+ ".VIEW_SEQUENCE";
private final StateManager parent;
private StateManager seamStateManager;
private final ComponentsLoader componentLoader;
private volatile int viewSequence = 0;
private Object viewSequenceMutex = "MUTEX";
private static final Log _log = LogFactory.getLog(AjaxStateManager.class);
/**
* @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 seamStateManagerClass = classLoader
.loadClass("org.jboss.seam.jsf.SeamStateManager");
Constructor constructor = seamStateManagerClass
.getConstructor(STATE_MANAGER_ARGUMENTS);
seamStateManager = (StateManager) constructor
.newInstance(new Object[] { new 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;
}
public SerializedView saveSerializedView(
FacesContext arg0) {
// delegate to enclosed class method.
return buildSerializedView(arg0);
}
public void writeState(FacesContext arg0,
SerializedView arg1) throws IOException {
// do nothing
}
} });
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) {
throw new UnsupportedOperationException();
}
/*
* (non-Javadoc)
*
* @see javax.faces.application.StateManager#getTreeStructureToSave(javax.faces.context.FacesContext)
*/
protected Object getTreeStructureToSave(FacesContext context) {
throw new UnsupportedOperationException();
}
/*
* (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, SerializedView state)
throws IOException {
parent.writeState(context, state);
if (_log.isDebugEnabled()) {
_log.debug("Write view state to the response");
}
context.getExternalContext().getRequestMap().put(
AjaxPhaseListener.VIEW_STATE_SAVED_PARAM, Boolean.TRUE);
}
/*
* (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();
TreeStrutureNode treeStructure = null;
Object[] state = null;
if (isSavingStateInClient(context)) {
treeStructure = (TreeStrutureNode) responseStateManager
.getTreeStructureToRestore(context, viewId);
// viewRoot = parent.restoreView(context, viewId, renderKitId);
state = (Object[]) responseStateManager
.getComponentStateToRestore(context);
} else {
Object[] serializedView = restoreStateFromSession(context, viewId,
renderKitId);
if (null != serializedView) {
treeStructure = (TreeStrutureNode) serializedView[0];
state = (Object[]) 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;
}
protected Object[] restoreStateFromSession(FacesContext context,
String viewId, String renderKitId) {
Object[] restoredState = null;
Object id = getRenderKit(context, renderKitId)
.getResponseStateManager().getTreeStructureToRestore(context,
viewId);
ExternalContext externalContext = context.getExternalContext();
Object session = externalContext.getSession(false);
if (null == session) {
if (_log.isDebugEnabled()) {
_log.debug("Can't restore view state : session expired");
}
} else {
synchronized (session) {
LRUMap viewStates = (LRUMap) externalContext.getSessionMap()
.get(VIEW_STATES_MAP);
if (null != viewStates) {
LRUMap logicalStates = (LRUMap) viewStates.get(viewId);
if (null != logicalStates) {
if (null != id) {
restoredState = (Object[]) logicalStates.get(id);
externalContext.getRequestMap().put(VIEW_SEQUENCE,
id);
if (null == restoredState) {
if (_log.isDebugEnabled()) {
_log
.debug("No saved view state found for a Id "
+ id
+ ". Restore last saved state");
}
restoredState = (Object[]) logicalStates
.get(logicalStates.lastKey());
}
} else {
if (_log.isDebugEnabled()) {
_log
.debug("No version Id for a saved view state in request. Restore last saved state");
}
restoredState = (Object[]) logicalStates
.get(logicalStates.lastKey());
}
} else if (_log.isDebugEnabled()) {
_log
.debug("Can't restore view state : no saved states for a ViewId "
+ viewId);
}
} else if (_log.isDebugEnabled()) {
_log
.debug("Can't restore view state : no saved view states in session");
}
}
}
return restoredState;
}
public SerializedView saveSerializedView(FacesContext context) {
if (null == seamStateManager) {
return buildSerializedView(context);
} else {
// Delegate save method to seam State Manager.
return seamStateManager.saveSerializedView(context);
}
}
/**
* @param context
* @return
*/
protected SerializedView buildSerializedView(FacesContext context) {
SerializedView serializedView = null;
UIViewRoot viewRoot = context.getViewRoot();
if (null != viewRoot && (!viewRoot.isTransient()) ) {
TreeStrutureNode treeStructure = new TreeStrutureNode();
treeStructure.apply(context, viewRoot, new HashSet());
Object treeState = viewRoot.processSaveState(context);
Object state[] = { treeState, getAdditionalState(context) };
if (isSavingStateInClient(context)) {
serializedView = new SerializedView(treeStructure, state);
} else {
serializedView = saveStateInSession(context, treeStructure,
state);
}
}
return serializedView;
}
/**
* @param context
* @param treeStructure
* @param state
* @return
*/
protected SerializedView saveStateInSession(FacesContext context,
Object treeStructure, Object state) {
SerializedView serializedView;
UIViewRoot viewRoot = context.getViewRoot();
ExternalContext externalContext = context.getExternalContext();
Object session = externalContext.getSession(true);
synchronized (session) {
LRUMap viewStates = (LRUMap) externalContext.getSessionMap().get(
VIEW_STATES_MAP);
if (null == viewStates) {
viewStates = new LRUMap(getNumberOfViews(externalContext));
externalContext.getSessionMap()
.put(VIEW_STATES_MAP, viewStates);
}
Object id = getNextViewId(context);
LRUMap logicalViewsMap = (LRUMap) viewStates.get(viewRoot
.getViewId());
if (null == logicalViewsMap) {
logicalViewsMap = new LRUMap(getNumberOfViews(externalContext));
}
// Renew last seen view.
viewStates.put(viewRoot.getViewId(), logicalViewsMap);
logicalViewsMap.put(id, new Object[] { treeStructure, state });
serializedView = new SerializedView(id, null);
}
return serializedView;
}
protected Object getAdditionalState(FacesContext context) {
return null;
}
protected void restoreAdditionalState(FacesContext context, Object state) {
}
protected Object getNextViewId(FacesContext context) {
AjaxContext ajaxContext = AjaxContext.getCurrentInstance(context);
if (ajaxContext.isAjaxRequest(context)) {
Object id = context.getExternalContext().getRequestMap().get(
VIEW_SEQUENCE);
if (null != id) {
return id;
}
}
synchronized (viewSequenceMutex) {
if (viewSequence++ == Character.MAX_VALUE) {
viewSequence = 0;
}
}
return UIViewRoot.UNIQUE_ID_PREFIX + ((int) viewSequence);
}
protected int getNumberOfViews(ExternalContext externalContext) {
return DEFAULT_NUMBER_OF_VIEWS;
}
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;
}
protected static final class TreeStrutureNode implements Externalizable {
/**
* TODO - implement Externalizable to reduce serialized state.
*/
private static final long serialVersionUID = -9038742487716977911L;
private static final String NULL_ID = "";
private Map facets = null;
private List children = null;
private String type;
private String id;
public TreeStrutureNode() {
}
public void apply(FacesContext context, UIComponent component,
Set uniqueIds) {
type = component.getClass().getName();
id = component.getId();
String clientId = component.getClientId(context);
if (!uniqueIds.add(clientId)) {
throw new IllegalStateException("duplicate Id for a component "
+ clientId);
}
Map componentFacets = component.getFacets();
for (Iterator i = componentFacets.entrySet().iterator(); i
.hasNext();) {
Entry element = (Entry) i.next();
UIComponent f = (UIComponent) element.getValue();
if (!f.isTransient()) {
TreeStrutureNode facet = new TreeStrutureNode();
facet.apply(context, f, uniqueIds);
if (null == facets) {
facets = new HashMap();
}
facets.put(element.getKey(), facet);
}
}
for (Iterator i = component.getChildren().iterator(); i.hasNext();) {
UIComponent child = (UIComponent) i.next();
if (!child.isTransient()) {
TreeStrutureNode t = new TreeStrutureNode();
t.apply(context, child, uniqueIds);
if (null == children) {
children = new ArrayList();
}
children.add(t);
}
}
}
public UIComponent restore(ComponentsLoader loader) {
UIComponent component;
component = loader.createComponent(type);
component.setId(id);
if (null != facets) {
for (Iterator i = facets.entrySet().iterator(); i.hasNext();) {
Entry element = (Entry) i.next();
UIComponent facet = ((TreeStrutureNode) element.getValue())
.restore(loader);
component.getFacets().put(element.getKey(), facet);
}
}
if (null != children) {
for (Iterator i = children.iterator(); i.hasNext();) {
TreeStrutureNode node = (TreeStrutureNode) i.next();
UIComponent child = node.restore(loader);
component.getChildren().add(child);
}
}
return component;
}
/**
* @return the facets
*/
public Map getFacets() {
return facets;
}
/**
* @param facets
* the facets to set
*/
public void setFacets(Map facets) {
this.facets = facets;
}
/**
* @return the children
*/
public List getChildren() {
return children;
}
/**
* @param children
* the children to set
*/
public void setChildren(List children) {
this.children = children;
}
/**
* @return the type
*/
public String getType() {
return type;
}
/**
* @param type
* the type to set
*/
public void setType(String type) {
this.type = type;
}
/**
* @return the id
*/
public String getId() {
return id;
}
/**
* @param id
* the id to set
*/
public void setId(String id) {
this.id = id;
}
public void readExternal(ObjectInput in) throws IOException,
ClassNotFoundException {
type = in.readUTF();
id = in.readUTF();
if (NULL_ID.equals(id)) {
id = null;
}
int facetsSize = in.readInt();
if (facetsSize > 0) {
facets = new HashMap(facetsSize);
for (int i = 0; i < facetsSize; i++) {
String facetName = in.readUTF();
TreeStrutureNode facet = new TreeStrutureNode();
facet.readExternal(in);
facets.put(facetName, facet);
}
}
int childrenSize = in.readInt();
if (childrenSize > 0) {
children = new ArrayList(childrenSize);
for (int i = 0; i < childrenSize; i++) {
TreeStrutureNode child = new TreeStrutureNode();
child.readExternal(in);
children.add(child);
}
}
}
public void writeExternal(ObjectOutput out) throws IOException {
out.writeUTF(type);
out.writeUTF(null == id ? NULL_ID : id);
if (null != facets) {
out.writeInt(facets.size());
for (Iterator i = facets.entrySet().iterator(); i.hasNext();) {
Map.Entry entry = (Map.Entry) i.next();
out.writeUTF((String) entry.getKey());
TreeStrutureNode node = (TreeStrutureNode) entry.getValue();
node.writeExternal(out);
}
} else {
out.writeInt(0);
}
if (null != children) {
out.writeInt(children.size());
for (Iterator i = children.iterator(); i.hasNext();) {
TreeStrutureNode child = (TreeStrutureNode) i.next();
child.writeExternal(out);
}
} else {
out.writeInt(0);
}
}
}
}