Java tutorial
/** * 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.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 Map<String, Class<?>> PRIMITIVE_CLASSES = new HashMap<String, Class<?>>(9, 1.0F); static { PRIMITIVE_CLASSES.put("boolean", boolean.class); PRIMITIVE_CLASSES.put("byte", byte.class); PRIMITIVE_CLASSES.put("char", char.class); PRIMITIVE_CLASSES.put("short", short.class); PRIMITIVE_CLASSES.put("int", int.class); PRIMITIVE_CLASSES.put("long", long.class); PRIMITIVE_CLASSES.put("float", float.class); PRIMITIVE_CLASSES.put("double", double.class); PRIMITIVE_CLASSES.put("void", void.class); } 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 { String name = desc.getName(); try { return Class.forName(name, true, Thread.currentThread().getContextClassLoader()); } catch (ClassNotFoundException cnfe) { Class<?> clazz = PRIMITIVE_CLASSES.get(name); if (clazz != null) { return clazz; } else { throw cnfe; } } } }; 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(context, viewId, id); if (restoredState != null && id != null) { context.getExternalContext().getRequestMap().put(AJAX_VIEW_SEQUENCE, 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(context, 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); 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; } }