at.irian.myfaces.wscope.renderkit.html.WsServerSideStateCacheImpl.java Source code

Java tutorial

Introduction

Here is the source code for at.irian.myfaces.wscope.renderkit.html.WsServerSideStateCacheImpl.java

Source

/*
 * 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 at.irian.myfaces.wscope.renderkit.html;

import org.apache.commons.collections.map.AbstractReferenceMap;
import org.apache.commons.collections.map.ReferenceMap;
import org.apache.myfaces.application.StateCache;
import org.apache.myfaces.buildtools.maven2.plugin.builder.annotation.JSFWebConfigParam;
import org.apache.myfaces.extensions.cdi.jsf.impl.util.RequestCache;
import org.apache.myfaces.shared.renderkit.RendererUtils;
import org.apache.myfaces.shared.util.MyFacesObjectInputStream;
import org.apache.myfaces.shared.util.WebConfigParamUtils;

import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import java.io.*;
import java.security.AccessController;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;

class WsServerSideStateCacheImpl extends StateCache<Object, Object> {
    private static final Logger log = Logger.getLogger(WsServerSideStateCacheImpl.class.getName());

    private static final String SERIALIZED_VIEW_SESSION_ATTR = WsServerSideStateCacheImpl.class.getName()
            + ".SERIALIZED_VIEW";

    private static final String RESTORED_SERIALIZED_VIEW_REQUEST_ATTR = WsServerSideStateCacheImpl.class.getName()
            + ".RESTORED_SERIALIZED_VIEW";

    private static final String RESTORED_VIEW_KEY_REQUEST_ATTR = WsServerSideStateCacheImpl.class.getName()
            + ".RESTORED_VIEW_KEY";

    /**
     * Defines the amount (default = 20) of the latest views are stored in session.
     * 
     * <p>Only applicable if state saving method is "server" (= default).
     * </p>
     * 
     */
    @JSFWebConfigParam(defaultValue = "20", since = "1.1", classType = "java.lang.Integer", group = "state", tags = "performance")
    private static final String NUMBER_OF_VIEWS_IN_SESSION_PARAM = "org.apache.myfaces.NUMBER_OF_VIEWS_IN_SESSION";

    /**
     * Indicates the amount of views (default is not active) that should be stored in session between sequential
     * POST or POST-REDIRECT-GET if org.apache.myfaces.USE_FLASH_SCOPE_PURGE_VIEWS_IN_SESSION is true.
     * 
     * <p>Only applicable if state saving method is "server" (= default). For example, if this param has value = 2 and 
     * in your custom webapp there is a form that is clicked 3 times, only 2 views
     * will be stored and the third one (the one stored the first time) will be
     * removed from session, even if the view can
     * store more sessions org.apache.myfaces.NUMBER_OF_VIEWS_IN_SESSION.
     * This feature becomes useful for multi-window applications.
     * where without this feature a window can swallow all view slots so
     * the other ones will throw ViewExpiredException.</p>
     */
    @JSFWebConfigParam(since = "2.0.6", classType = "java.lang.Integer", group = "state", tags = "performance")
    private static final String NUMBER_OF_SEQUENTIAL_VIEWS_IN_SESSION_PARAM = "org.apache.myfaces.NUMBER_OF_SEQUENTIAL_VIEWS_IN_SESSION";

    /**
     * Default value for <code>org.apache.myfaces.NUMBER_OF_VIEWS_IN_SESSION</code> context parameter.
     */
    private static final int DEFAULT_NUMBER_OF_VIEWS_IN_SESSION = 20;

    /**
     * Indicate if the state should be serialized before save it on the session. 
     * <p>
     * Only applicable if state saving method is "server" (= default).
     * If <code>true</code> (default) the state will be serialized to a byte stream before it is written to the session.
     * If <code>false</code> the state will not be serialized to a byte stream.
     * </p>
     */
    @JSFWebConfigParam(defaultValue = "true", since = "1.1", expectedValues = "true,false", group = "state", tags = "performance")
    private static final String SERIALIZE_STATE_IN_SESSION_PARAM = "org.apache.myfaces.SERIALIZE_STATE_IN_SESSION";

    /**
     * Indicates that the serialized state will be compressed before it is written to the session. By default true.
     * 
     * Only applicable if state saving method is "server" (= default) and if
     * <code>org.apache.myfaces.SERIALIZE_STATE_IN_SESSION</code> is <code>true</code> (= default).
     * If <code>true</code> (default) the serialized state will be compressed before it is written to the session.
     * If <code>false</code> the state will not be compressed.
     */
    @JSFWebConfigParam(defaultValue = "true", since = "1.1", expectedValues = "true,false", group = "state", tags = "performance")
    private static final String COMPRESS_SERVER_STATE_PARAM = "org.apache.myfaces.COMPRESS_STATE_IN_SESSION";

    /**
     * Default value for <code>org.apache.myfaces.COMPRESS_STATE_IN_SESSION</code> context parameter.
     */
    private static final boolean DEFAULT_COMPRESS_SERVER_STATE_PARAM = true;

    /**
     * Default value for <code>org.apache.myfaces.SERIALIZE_STATE_IN_SESSION</code> context parameter.
     */
    private static final boolean DEFAULT_SERIALIZE_STATE_IN_SESSION = true;

    /**
     * Define the way of handle old view references(views removed from session), making possible to
     * store it in a cache, so the state manager first try to get the view from the session. If is it
     * not found and soft or weak ReferenceMap is used, it try to get from it.
     * <p>
     * Only applicable if state saving method is "server" (= default).
     * </p>
     * <p>
     * The gc is responsible for remove the views, according to the rules used for soft, weak or phantom
     * references. If a key in soft and weak mode is garbage collected, its values are purged.
     * </p>
     * <p>
     * By default no cache is used, so views removed from session became phantom references.
     * </p>
     * <ul> 
     * <li> off, no: default, no cache is used</li> 
     * <li> hard-soft: use an ReferenceMap(AbstractReferenceMap.HARD, AbstractReferenceMap.SOFT)</li>
     * <li> soft: use an ReferenceMap(AbstractReferenceMap.SOFT, AbstractReferenceMap.SOFT, true) </li>
     * <li> soft-weak: use an ReferenceMap(AbstractReferenceMap.SOFT, AbstractReferenceMap.WEAK, true) </li>
     * <li> weak: use an ReferenceMap(AbstractReferenceMap.WEAK, AbstractReferenceMap.WEAK, true) </li>
     * </ul>
     * 
     */
    @JSFWebConfigParam(defaultValue = "off", expectedValues = "off, no, hard-soft, soft, soft-weak, weak", since = "1.2.5", group = "state", tags = "performance")
    private static final String CACHE_OLD_VIEWS_IN_SESSION_MODE = "org.apache.myfaces.CACHE_OLD_VIEWS_IN_SESSION_MODE";

    /**
     * This option uses an hard-soft ReferenceMap, but it could cause a 
     * memory leak, because the keys are not removed by any method
     * (MYFACES-1660). So use with caution.
     */
    private static final String CACHE_OLD_VIEWS_IN_SESSION_MODE_HARD_SOFT = "hard-soft";

    private static final String CACHE_OLD_VIEWS_IN_SESSION_MODE_SOFT = "soft";

    private static final String CACHE_OLD_VIEWS_IN_SESSION_MODE_SOFT_WEAK = "soft-weak";

    private static final String CACHE_OLD_VIEWS_IN_SESSION_MODE_WEAK = "weak";

    private static final String CACHE_OLD_VIEWS_IN_SESSION_MODE_OFF = "off";

    /**
     * Allow use flash scope to keep track of the views used in session and the previous ones,
     * so server side state saving can delete old views even if POST-REDIRECT-GET pattern is used.
     * 
     * <p>
     * Only applicable if state saving method is "server" (= default).
     * The default value is false.</p>
     */
    @JSFWebConfigParam(since = "2.0.6", defaultValue = "false", expectedValues = "true, false", group = "state")
    private static final String USE_FLASH_SCOPE_PURGE_VIEWS_IN_SESSION = "org.apache.myfaces.USE_FLASH_SCOPE_PURGE_VIEWS_IN_SESSION";

    private static final int UNCOMPRESSED_FLAG = 0;
    private static final int COMPRESSED_FLAG = 1;

    private static final int JSF_SEQUENCE_INDEX = 0;

    private Boolean _useFlashScopePurgeViewsInSession = null;

    private Integer _numberOfSequentialViewsInSession = null;
    private boolean _numberOfSequentialViewsInSessionSet = false;

    //------------------------------------- METHODS COPIED FROM JspStateManagerImpl--------------------------------

    private String calcWindowId(FacesContext context) {
        String windowId = context.getExternalContext().getRequestParameterMap().get("windowId");
        if (windowId == null) {
            windowId = RequestCache.getWindowContextManager().getCurrentWindowContext().getId();
        }
        return windowId;
    }

    protected Integer getServerStateId(Object[] state) {
        if (state != null) {
            Object serverStateId = state[JSF_SEQUENCE_INDEX];
            if (serverStateId != null) {
                return Integer.valueOf((String) serverStateId, Character.MAX_RADIX);
            }
        }
        return null;
    }

    @Override
    public String createCryptographicallyStrongTokenFromSession(FacesContext context) {
        return null;
    }

    protected void saveSerializedViewInServletSession(FacesContext context, Object serializedView) {
        String windowId = calcWindowId(context);

        Map<String, Object> sessionMap = context.getExternalContext().getSessionMap();
        WindowCollection windowCollection = WindowCollection.fromSession(context);
        if (!windowCollection.containsWindowId(windowId)) {
            windowCollection.initWindowId(windowId);
        }

        Map<Object, Object> attributeMap = context.getAttributes();

        SerializedViewKey key = null;
        if (getNumberOfSequentialViewsInSession(context.getExternalContext()) != null
                && getNumberOfSequentialViewsInSession(context.getExternalContext()) > 0) {
            key = (SerializedViewKey) attributeMap.get(RESTORED_VIEW_KEY_REQUEST_ATTR);

            if (key == null) {
                if (isUseFlashScopePurgeViewsInSession(context.getExternalContext()) && Boolean.TRUE.equals(
                        context.getExternalContext().getRequestMap().get("oam.Flash.REDIRECT.PREVIOUSREQUEST"))) {
                    key = (SerializedViewKey) context.getExternalContext().getFlash()
                            .get(RESTORED_VIEW_KEY_REQUEST_ATTR);
                }
            }
        }

        SerializedViewCollection viewCollection = windowCollection.getViewCollection(windowId);

        viewCollection.add(context, serializeView(context, serializedView), getNextViewSequence(context), key);

        // replace the value to notify the container about the change
        sessionMap.put(SERIALIZED_VIEW_SESSION_ATTR, windowCollection);
    }

    protected Object getSerializedViewFromServletSession(FacesContext context, String windowId, String viewId,
            Integer sequence) {
        ExternalContext externalContext = context.getExternalContext();
        Map<Object, Object> attributeMap = context.getAttributes();
        Object serializedView = null;
        if (attributeMap.containsKey(RESTORED_SERIALIZED_VIEW_REQUEST_ATTR)) {
            serializedView = attributeMap.get(RESTORED_SERIALIZED_VIEW_REQUEST_ATTR);
        } else {
            WindowCollection windowCollection = (WindowCollection) externalContext.getSessionMap()
                    .get(SERIALIZED_VIEW_SESSION_ATTR);
            if (windowCollection != null) {

                if (sequence != null) {
                    Object state = windowCollection.getViewCollection(windowId).get(sequence, viewId);
                    if (state != null) {
                        serializedView = deserializeView(state);
                    }
                }
            }
            attributeMap.put(RESTORED_SERIALIZED_VIEW_REQUEST_ATTR, serializedView);

            if (getNumberOfSequentialViewsInSession(externalContext) != null
                    && getNumberOfSequentialViewsInSession(externalContext) > 0) {
                SerializedViewKey key = new SerializedViewKey(viewId, sequence);
                attributeMap.put(RESTORED_VIEW_KEY_REQUEST_ATTR, key);

                if (isUseFlashScopePurgeViewsInSession(externalContext)) {
                    externalContext.getFlash().put(RESTORED_VIEW_KEY_REQUEST_ATTR, key);
                    externalContext.getFlash().keep(RESTORED_VIEW_KEY_REQUEST_ATTR);
                }
            }

            nextViewSequence(context);
        }
        return serializedView;
    }

    public int getNextViewSequence(FacesContext context) {
        if (!context.getAttributes().containsKey(RendererUtils.SEQUENCE_PARAM)) {
            nextViewSequence(context);
        }

        Integer sequence = (Integer) context.getAttributes().get(RendererUtils.SEQUENCE_PARAM);
        return sequence.intValue();
    }

    public void nextViewSequence(FacesContext facescontext) {
        ExternalContext externalContext = facescontext.getExternalContext();
        Object sessionObj = externalContext.getSession(true);
        Integer sequence;
        synchronized (sessionObj) // synchronized to increase sequence if multiple requests
                                  // are handled at the same time for the session
        {
            Map<String, Object> map = externalContext.getSessionMap();
            sequence = (Integer) map.get(RendererUtils.SEQUENCE_PARAM);
            if (sequence == null || sequence.intValue() == Integer.MAX_VALUE) {
                sequence = Integer.valueOf(1);
            } else {
                sequence = Integer.valueOf(sequence.intValue() + 1);
            }
            map.put(RendererUtils.SEQUENCE_PARAM, sequence);
        }
        facescontext.getAttributes().put(RendererUtils.SEQUENCE_PARAM, sequence);
    }

    protected Object serializeView(FacesContext context, Object serializedView) {
        if (log.isLoggable(Level.FINEST)) {
            log.finest("Entering serializeView");
        }

        if (isSerializeStateInSession(context)) {
            if (log.isLoggable(Level.FINEST)) {
                log.finest("Processing serializeView - serialize state in session");
            }

            ByteArrayOutputStream baos = new ByteArrayOutputStream(1024);
            try {
                OutputStream os = baos;
                if (isCompressStateInSession(context)) {
                    if (log.isLoggable(Level.FINEST)) {
                        log.finest("Processing serializeView - serialize compressed");
                    }

                    os.write(COMPRESSED_FLAG);
                    os = new GZIPOutputStream(os, 1024);
                } else {
                    if (log.isLoggable(Level.FINEST)) {
                        log.finest("Processing serializeView - serialize uncompressed");
                    }

                    os.write(UNCOMPRESSED_FLAG);
                }

                //Object[] stateArray = (Object[]) serializedView;

                ObjectOutputStream out = new ObjectOutputStream(os);

                out.writeObject(serializedView);
                //out.writeObject(stateArray[0]);
                //out.writeObject(stateArray[1]);
                out.close();
                baos.close();

                if (log.isLoggable(Level.FINEST)) {
                    log.finest("Exiting serializeView - serialized. Bytes : " + baos.size());
                }
                return baos.toByteArray();
            } catch (IOException e) {
                log.log(Level.SEVERE, "Exiting serializeView - Could not serialize state: " + e.getMessage(), e);
                return null;
            }
        }

        if (log.isLoggable(Level.FINEST)) {
            log.finest("Exiting serializeView - do not serialize state in session.");
        }

        return serializedView;

    }

    /**
     * Reads the value of the <code>org.apache.myfaces.SERIALIZE_STATE_IN_SESSION</code> context parameter.
     * @see #SERIALIZE_STATE_IN_SESSION_PARAM
     * @param context <code>FacesContext</code> for the request we are processing.
     * @return boolean true, if the server state should be serialized in the session
     */
    protected boolean isSerializeStateInSession(FacesContext context) {
        String value = context.getExternalContext().getInitParameter(SERIALIZE_STATE_IN_SESSION_PARAM);
        boolean serialize = DEFAULT_SERIALIZE_STATE_IN_SESSION;
        if (value != null) {
            serialize = Boolean.valueOf(value);
        }
        return serialize;
    }

    /**
     * Reads the value of the <code>org.apache.myfaces.COMPRESS_STATE_IN_SESSION</code> context parameter.
     * @see #COMPRESS_SERVER_STATE_PARAM
     * @param context <code>FacesContext</code> for the request we are processing.
     * @return boolean true, if the server state steam should be compressed
     */
    protected boolean isCompressStateInSession(FacesContext context) {
        String value = context.getExternalContext().getInitParameter(COMPRESS_SERVER_STATE_PARAM);
        boolean compress = DEFAULT_COMPRESS_SERVER_STATE_PARAM;
        if (value != null) {
            compress = Boolean.valueOf(value);
        }
        return compress;
    }

    protected Object deserializeView(Object state) {
        if (log.isLoggable(Level.FINEST)) {
            log.finest("Entering deserializeView");
        }

        if (state instanceof byte[]) {
            if (log.isLoggable(Level.FINEST)) {
                log.finest("Processing deserializeView - deserializing serialized state. Bytes : "
                        + ((byte[]) state).length);
            }

            try {
                ByteArrayInputStream bais = new ByteArrayInputStream((byte[]) state);
                InputStream is = bais;
                if (is.read() == COMPRESSED_FLAG) {
                    is = new GZIPInputStream(is);
                }
                ObjectInputStream ois = null;
                try {
                    final ObjectInputStream in = new MyFacesObjectInputStream(is);
                    ois = in;
                    Object object = null;
                    if (System.getSecurityManager() != null) {
                        object = AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() {
                            public Object run()
                                    throws PrivilegedActionException, IOException, ClassNotFoundException {
                                //return new Object[] {in.readObject(), in.readObject()};
                                return in.readObject();
                            }
                        });
                    } else {
                        //object = new Object[] {in.readObject(), in.readObject()};
                        object = in.readObject();
                    }
                    return object;
                } finally {
                    if (ois != null) {
                        ois.close();
                        ois = null;
                    }
                }
            } catch (PrivilegedActionException e) {
                log.log(Level.SEVERE, "Exiting deserializeView - Could not deserialize state: " + e.getMessage(),
                        e);
                return null;
            } catch (IOException e) {
                log.log(Level.SEVERE, "Exiting deserializeView - Could not deserialize state: " + e.getMessage(),
                        e);
                return null;
            } catch (ClassNotFoundException e) {
                log.log(Level.SEVERE, "Exiting deserializeView - Could not deserialize state: " + e.getMessage(),
                        e);
                return null;
            }
        } else if (state instanceof Object[]) {
            if (log.isLoggable(Level.FINEST)) {
                log.finest("Exiting deserializeView - state not serialized.");
            }

            return state;
        } else if (state == null) {
            log.severe("Exiting deserializeView - this method should not be called with a null-state.");
            return null;
        } else {
            log.severe("Exiting deserializeView - this method should not be called with a state of type : "
                    + state.getClass());
            return null;
        }
    }

    protected static class SerializedViewCollection implements Serializable {
        private static final long serialVersionUID = -3734849062185115847L;

        private final List<SerializedViewKey> _keys = new ArrayList<SerializedViewKey>(
                DEFAULT_NUMBER_OF_VIEWS_IN_SESSION);
        private final Map<SerializedViewKey, Object> _serializedViews = new HashMap<SerializedViewKey, Object>();

        private final Map<SerializedViewKey, SerializedViewKey> _precedence = new HashMap<SerializedViewKey, SerializedViewKey>();

        // old views will be hold as soft references which will be removed by
        // the garbage collector if free memory is low
        private transient Map<Object, Object> _oldSerializedViews = null;

        public synchronized void add(FacesContext context, Object state, Integer nextSequence,
                SerializedViewKey previousRestoredKey) {
            SerializedViewKey key = new SerializedViewKey(context.getViewRoot().getViewId(), nextSequence);
            _serializedViews.put(key, state);

            Integer maxCount = getNumberOfSequentialViewsInSession(context);
            if (maxCount != null) {
                if (previousRestoredKey != null) {
                    _precedence.put((SerializedViewKey) key, previousRestoredKey);
                }
            }

            while (_keys.remove(key)) {
                // do nothing
            }
            _keys.add(key);

            if (previousRestoredKey != null && maxCount != null && maxCount > 0) {
                int count = 0;
                SerializedViewKey previousKey = (SerializedViewKey) key;
                do {
                    previousKey = _precedence.get(previousKey);
                    count++;
                } while (previousKey != null && count < maxCount);

                if (previousKey != null) {
                    SerializedViewKey keyToRemove = (SerializedViewKey) previousKey;
                    // In theory it should be only one key but just to be sure
                    // do it in a loop, but in this case if cache old views is on,
                    // put on that map.
                    do {
                        while (_keys.remove(keyToRemove)) {
                            // do nothing
                        }

                        Object oldView = _serializedViews.remove(keyToRemove);
                        if (oldView != null && !CACHE_OLD_VIEWS_IN_SESSION_MODE_OFF
                                .equals(getCacheOldViewsInSessionMode(context))) {
                            getOldSerializedViewsMap().put(keyToRemove, oldView);
                        }

                        keyToRemove = _precedence.remove(keyToRemove);
                    } while (keyToRemove != null);
                }
            }

            int views = getNumberOfViewsInSession(context);
            while (_keys.size() > views) {
                key = _keys.remove(0);

                if (maxCount != null && maxCount > 0) {
                    SerializedViewKey keyToRemove = (SerializedViewKey) key;
                    // Note in this case the key to delete is the oldest one, 
                    // so it could be at least one precedence, but to be safe
                    // do it with a loop.
                    do {
                        keyToRemove = _precedence.remove(keyToRemove);
                    } while (keyToRemove != null);
                }

                Object oldView = _serializedViews.remove(key);
                if (oldView != null
                        && !CACHE_OLD_VIEWS_IN_SESSION_MODE_OFF.equals(getCacheOldViewsInSessionMode(context))) {
                    getOldSerializedViewsMap().put(key, oldView);
                }
            }
        }

        protected Integer getNumberOfSequentialViewsInSession(FacesContext context) {
            return WebConfigParamUtils.getIntegerInitParameter(context.getExternalContext(),
                    NUMBER_OF_SEQUENTIAL_VIEWS_IN_SESSION_PARAM);
        }

        /**
         * Reads the amount (default = 20) of views to be stored in session.
         * @see #NUMBER_OF_VIEWS_IN_SESSION_PARAM
         * @param context FacesContext for the current request, we are processing
         * @return Number vf views stored in the session
         */
        protected int getNumberOfViewsInSession(FacesContext context) {
            String value = context.getExternalContext().getInitParameter(NUMBER_OF_VIEWS_IN_SESSION_PARAM);
            int views = DEFAULT_NUMBER_OF_VIEWS_IN_SESSION;
            if (value != null) {
                try {
                    views = Integer.parseInt(value);
                    if (views <= 0) {
                        log.severe("Configured value for " + NUMBER_OF_VIEWS_IN_SESSION_PARAM
                                + " is not valid, must be an value > 0, using default value ("
                                + DEFAULT_NUMBER_OF_VIEWS_IN_SESSION);
                        views = DEFAULT_NUMBER_OF_VIEWS_IN_SESSION;
                    }
                } catch (Throwable e) {
                    log.log(Level.SEVERE,
                            "Error determining the value for " + NUMBER_OF_VIEWS_IN_SESSION_PARAM
                                    + ", expected an integer value > 0, using default value ("
                                    + DEFAULT_NUMBER_OF_VIEWS_IN_SESSION + "): " + e.getMessage(),
                            e);
                }
            }
            return views;
        }

        /**
         * @return old serialized views map
         */
        @SuppressWarnings("unchecked")
        protected Map<Object, Object> getOldSerializedViewsMap() {
            FacesContext context = FacesContext.getCurrentInstance();
            if (_oldSerializedViews == null && context != null) {
                String cacheMode = getCacheOldViewsInSessionMode(context);
                if (CACHE_OLD_VIEWS_IN_SESSION_MODE_WEAK.equals(cacheMode)) {
                    _oldSerializedViews = new ReferenceMap(AbstractReferenceMap.WEAK, AbstractReferenceMap.WEAK,
                            true);
                } else if (CACHE_OLD_VIEWS_IN_SESSION_MODE_SOFT_WEAK.equals(cacheMode)) {
                    _oldSerializedViews = new ReferenceMap(AbstractReferenceMap.SOFT, AbstractReferenceMap.WEAK,
                            true);
                } else if (CACHE_OLD_VIEWS_IN_SESSION_MODE_SOFT.equals(cacheMode)) {
                    _oldSerializedViews = new ReferenceMap(AbstractReferenceMap.SOFT, AbstractReferenceMap.SOFT,
                            true);
                } else if (CACHE_OLD_VIEWS_IN_SESSION_MODE_HARD_SOFT.equals(cacheMode)) {
                    _oldSerializedViews = new ReferenceMap(AbstractReferenceMap.HARD, AbstractReferenceMap.SOFT);
                }
            }

            return _oldSerializedViews;
        }

        /**
         * Reads the value of the <code>org.apache.myfaces.CACHE_OLD_VIEWS_IN_SESSION_MODE</code> context parameter.
         * 
         * @since 1.2.5
         * @param context
         * @return constant indicating caching mode
         * @see #CACHE_OLD_VIEWS_IN_SESSION_MODE
         */
        protected String getCacheOldViewsInSessionMode(FacesContext context) {
            String value = context.getExternalContext().getInitParameter(CACHE_OLD_VIEWS_IN_SESSION_MODE);
            if (value == null) {
                return CACHE_OLD_VIEWS_IN_SESSION_MODE_OFF;
            } else if (value.equalsIgnoreCase(CACHE_OLD_VIEWS_IN_SESSION_MODE_SOFT)) {
                return CACHE_OLD_VIEWS_IN_SESSION_MODE_SOFT;
            } else if (value.equalsIgnoreCase(CACHE_OLD_VIEWS_IN_SESSION_MODE_SOFT_WEAK)) {
                return CACHE_OLD_VIEWS_IN_SESSION_MODE_SOFT_WEAK;
            } else if (value.equalsIgnoreCase(CACHE_OLD_VIEWS_IN_SESSION_MODE_WEAK)) {
                return CACHE_OLD_VIEWS_IN_SESSION_MODE_WEAK;
            } else if (value.equalsIgnoreCase(CACHE_OLD_VIEWS_IN_SESSION_MODE_HARD_SOFT)) {
                return CACHE_OLD_VIEWS_IN_SESSION_MODE_HARD_SOFT;
            } else {
                return CACHE_OLD_VIEWS_IN_SESSION_MODE_OFF;
            }
        }

        public Object get(Integer sequence, String viewId) {
            Object key = new SerializedViewKey(viewId, sequence);
            Object value = _serializedViews.get(key);
            if (value == null) {
                Map<Object, Object> oldSerializedViewMap = getOldSerializedViewsMap();
                if (oldSerializedViewMap != null) {
                    value = oldSerializedViewMap.get(key);
                }
            }
            return value;
        }
    }

    protected static class SerializedViewKey implements Serializable {
        private static final long serialVersionUID = -1170697124386063642L;

        private final String _viewId;
        private final Integer _sequenceId;

        public SerializedViewKey(String viewId, Integer sequence) {
            _sequenceId = sequence;
            _viewId = viewId;
        }

        /*
        public SerializedViewKey(FacesContext context)
        {
        _sequenceId = getNextViewSequence(context);
        _viewId = context.getViewRoot().getViewId();
        }*/

        @Override
        public int hashCode() {
            final int prime = 31;
            int result = 1;
            result = prime * result + ((_sequenceId == null) ? 0 : _sequenceId.hashCode());
            result = prime * result + ((_viewId == null) ? 0 : _viewId.hashCode());
            return result;
        }

        @Override
        public boolean equals(Object obj) {
            if (this == obj) {
                return true;
            }
            if (obj == null) {
                return false;
            }
            if (getClass() != obj.getClass()) {
                return false;
            }
            final SerializedViewKey other = (SerializedViewKey) obj;
            if (_sequenceId == null) {
                if (other._sequenceId != null) {
                    return false;
                }
            } else if (!_sequenceId.equals(other._sequenceId)) {
                return false;
            }
            if (_viewId == null) {
                if (other._viewId != null) {
                    return false;
                }
            } else if (!_viewId.equals(other._viewId)) {
                return false;
            }
            return true;
        }

    }

    //------------------------------------- METHOD FROM StateCache ------------------------------------------------

    @Override
    public Object saveSerializedView(FacesContext facesContext, Object serializedView) {
        if (log.isLoggable(Level.FINEST)) {
            log.finest("Processing saveSerializedView - server-side state saving - save state");
        }
        //save state in server session
        saveSerializedViewInServletSession(facesContext, serializedView);

        if (log.isLoggable(Level.FINEST)) {
            log.finest("Exiting saveSerializedView - server-side state saving - saved state");
        }

        return encodeSerializedState(facesContext, serializedView);
    }

    @Override
    public Object restoreSerializedView(FacesContext facesContext, String viewId, Object viewState) {
        if (log.isLoggable(Level.FINEST)) {
            log.finest("Restoring view from session");
        }

        Integer serverStateId = getServerStateId((Object[]) viewState);

        String windowId = calcWindowId(facesContext);

        return (serverStateId == null) ? null
                : getSerializedViewFromServletSession(facesContext, windowId, viewId, serverStateId);
    }

    public Object encodeSerializedState(FacesContext facesContext, Object serializedView) {
        Object[] identifier = new Object[2];
        identifier[JSF_SEQUENCE_INDEX] = Integer.toString(getNextViewSequence(facesContext), Character.MAX_RADIX);
        return identifier;
    }

    @Override
    public boolean isWriteStateAfterRenderViewRequired(FacesContext facesContext) {
        return false;
    }

    //------------------------------------- Custom methods -----------------------------------------------------

    private boolean isUseFlashScopePurgeViewsInSession(ExternalContext externalContext) {
        if (_useFlashScopePurgeViewsInSession == null) {
            _useFlashScopePurgeViewsInSession = WebConfigParamUtils.getBooleanInitParameter(externalContext,
                    USE_FLASH_SCOPE_PURGE_VIEWS_IN_SESSION, false);
        }
        return _useFlashScopePurgeViewsInSession;
    }

    private Integer getNumberOfSequentialViewsInSession(ExternalContext externalContext) {
        if (!_numberOfSequentialViewsInSessionSet) {
            _numberOfSequentialViewsInSession = WebConfigParamUtils.getIntegerInitParameter(externalContext,
                    NUMBER_OF_SEQUENTIAL_VIEWS_IN_SESSION_PARAM);
            _numberOfSequentialViewsInSessionSet = true;
        }
        return _numberOfSequentialViewsInSession;
    }

    static final class WindowCollection {
        private ConcurrentHashMap<String, SerializedViewCollection> viewCollections;

        private WindowCollection() {
            this.viewCollections = new ConcurrentHashMap<>();
        }

        static WindowCollection fromSession(FacesContext facesContext) {
            WindowCollection windowCollection = (WindowCollection) facesContext.getExternalContext().getSessionMap()
                    .get(SERIALIZED_VIEW_SESSION_ATTR);
            if (windowCollection == null) {
                Object session = facesContext.getExternalContext().getSession(false);
                if (session != null) {
                    //noinspection SynchronizationOnLocalVariableOrMethodParameter
                    synchronized (session) {
                        windowCollection = new WindowCollection();
                    }
                } else {
                    windowCollection = new WindowCollection();
                }
            }
            return windowCollection;
        }

        void initWindowId(String windowId) {
            if (viewCollections.containsKey(windowId)) {
                throw new IllegalStateException("WindowCollection already contains windowId " + windowId);
            }
            this.viewCollections.put(windowId, new SerializedViewCollection());
        }

        SerializedViewCollection getViewCollection(String windowId) {
            if (!viewCollections.containsKey(windowId)) {
                initWindowId(windowId);
            }
            return viewCollections.get(windowId);
        }

        boolean containsWindowId(String windowId) {
            return viewCollections.containsKey(windowId);
        }
    }
}