org.nuxeo.ecm.platform.ui.web.application.NuxeoServerSideStateHelper.java Source code

Java tutorial

Introduction

Here is the source code for org.nuxeo.ecm.platform.ui.web.application.NuxeoServerSideStateHelper.java

Source

/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 1997-2013 Oracle and/or its affiliates. All rights reserved.
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License.  You can
 * obtain a copy of the License at
 * https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
 * or packager/legal/LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at packager/legal/LICENSE.txt.
 *
 * GPL Classpath Exception:
 * Oracle designates this particular file as subject to the "Classpath"
 * exception as provided by Oracle in the GPL Version 2 section of the License
 * file that accompanied this code.
 *
 * Modifications:
 * If applicable, add the following below the License Header, with the fields
 * enclosed by brackets [] replaced by your own identifying information:
 * "Portions Copyright [year] [name of copyright owner]"
 *
 * Contributor(s):
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */

package org.nuxeo.ecm.platform.ui.web.application;

import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import java.io.IOException;

import javax.faces.FacesException;
import javax.faces.context.FacesContext;
import javax.faces.context.ExternalContext;
import javax.faces.context.ResponseWriter;
import javax.faces.component.UIViewRoot;

import com.sun.faces.util.TypedCollections;
import com.sun.faces.util.LRUMap;
import com.sun.faces.util.Util;
import com.sun.faces.util.RequestStateManager;

import static com.sun.faces.config.WebConfiguration.BooleanWebContextInitParameter.AutoCompleteOffOnViewState;
import static com.sun.faces.config.WebConfiguration.BooleanWebContextInitParameter.EnableViewStateIdRendering;

import javax.faces.render.ResponseStateManager;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jboss.seam.contexts.Contexts;
import org.jboss.seam.core.Conversation;

import com.sun.faces.renderkit.ServerSideStateHelper;

/**
 * @since 6.0
 */
public class NuxeoServerSideStateHelper extends ServerSideStateHelper {

    private static final Log log = LogFactory.getLog(NuxeoServerSideStateHelper.class);

    public static final int DEFAULT_NUMBER_OF_CONVERSATIONS_IN_SESSION = 4;

    public static final String NUMBER_OF_CONVERSATIONS_IN_SESSION = "nuxeo.jsf.numberOfConversationsInSession";

    protected static final String NO_LONGRUNNING_CONVERSATION_ID = "NOLRC";

    protected static Integer numbersOfConversationsInSession = null;

    /**
     * The top level attribute name for storing the state structures within the session.
     */
    public static final String CONVERSATION_VIEW_MAP = NuxeoServerSideStateHelper.class.getName()
            + ".ConversationViewMap";

    protected static int getNbOfConversationsInSession(FacesContext context) {
        if (numbersOfConversationsInSession == null) {
            ExternalContext externalContext = context.getExternalContext();
            String value = externalContext.getInitParameter(NUMBER_OF_CONVERSATIONS_IN_SESSION);
            if (null == value) {
                numbersOfConversationsInSession = DEFAULT_NUMBER_OF_CONVERSATIONS_IN_SESSION;
            } else {
                try {
                    numbersOfConversationsInSession = Integer.parseInt(value);

                } catch (NumberFormatException e) {
                    throw new FacesException(
                            "Context parameter " + NUMBER_OF_CONVERSATIONS_IN_SESSION + " must have integer value");
                }
            }
        }
        return numbersOfConversationsInSession;
    }

    @Override
    public void writeState(FacesContext ctx, Object state, StringBuilder stateCapture) throws IOException {

        Util.notNull("context", ctx);

        String id;

        if (!ctx.getViewRoot().isTransient()) {
            Util.notNull("state", state);
            Object[] stateToWrite = (Object[]) state;
            ExternalContext externalContext = ctx.getExternalContext();
            Object sessionObj = externalContext.getSession(true);
            Map<String, Object> sessionMap = externalContext.getSessionMap();

            // noinspection SynchronizationOnLocalVariableOrMethodParameter
            synchronized (sessionObj) {
                String conversationId = NO_LONGRUNNING_CONVERSATION_ID;
                if (Contexts.isConversationContextActive() && Conversation.instance().isLongRunning()) {
                    conversationId = Conversation.instance().getId();
                }

                Map<String, Map> conversationMap = TypedCollections
                        .dynamicallyCastMap((Map) sessionMap.get(CONVERSATION_VIEW_MAP), String.class, Map.class);
                if (conversationMap == null) {
                    conversationMap = new LRUMap<String, Map>(getNbOfConversationsInSession(ctx));
                    sessionMap.put(CONVERSATION_VIEW_MAP, conversationMap);
                }

                Map<String, Map> logicalMap = TypedCollections
                        .dynamicallyCastMap(conversationMap.get(conversationId), String.class, Map.class);
                if (logicalMap == null) {
                    if (conversationMap.size() == getNbOfConversationsInSession(ctx)) {
                        if (log.isDebugEnabled()) {
                            log.warn("Too many conversations, dumping the least recently used conversation ("
                                    + conversationMap.keySet().iterator().next() + ")");
                        }
                    }
                    logicalMap = new LRUMap<String, Map>(numberOfLogicalViews);
                    conversationMap.put(conversationId, logicalMap);
                }

                Object structure = stateToWrite[0];
                Object savedState = handleSaveState(stateToWrite[1]);

                String idInLogicalMap = (String) RequestStateManager.get(ctx, RequestStateManager.LOGICAL_VIEW_MAP);
                if (idInLogicalMap == null) {
                    idInLogicalMap = ((generateUniqueStateIds) ? createRandomId()
                            : createIncrementalRequestId(ctx));
                }
                String idInActualMap = null;
                if (ctx.getPartialViewContext().isPartialRequest()) {
                    // If partial request, do not change actual view Id, because
                    // page not actually changed.
                    // Otherwise partial requests will soon overflow cache with
                    // values that would be never used.
                    idInActualMap = (String) RequestStateManager.get(ctx, RequestStateManager.ACTUAL_VIEW_MAP);
                }
                if (null == idInActualMap) {
                    idInActualMap = ((generateUniqueStateIds) ? createRandomId() : createIncrementalRequestId(ctx));
                }
                Map<String, Object[]> actualMap = TypedCollections
                        .dynamicallyCastMap(logicalMap.get(idInLogicalMap), String.class, Object[].class);
                if (actualMap == null) {
                    actualMap = new LRUMap<String, Object[]>(numberOfViews);
                    logicalMap.put(idInLogicalMap, actualMap);
                }

                id = idInLogicalMap + ':' + idInActualMap;

                Object[] stateArray = actualMap.get(idInActualMap);
                // reuse the array if possible
                if (stateArray != null) {
                    stateArray[0] = structure;
                    stateArray[1] = savedState;
                } else {
                    actualMap.put(idInActualMap, new Object[] { structure, savedState });
                }

                conversationMap.put(conversationId, logicalMap);
                // always call put/setAttribute as we may be in a clustered
                // environment.
                sessionMap.put(CONVERSATION_VIEW_MAP, conversationMap);
            }
        } else {
            id = "stateless";
        }

        if (stateCapture != null) {
            stateCapture.append(id);
        } else {
            ResponseWriter writer = ctx.getResponseWriter();

            writer.startElement("input", null);
            writer.writeAttribute("type", "hidden", null);
            writer.writeAttribute("name", ResponseStateManager.VIEW_STATE_PARAM, null);
            if (webConfig.isOptionEnabled(EnableViewStateIdRendering)) {
                String viewStateId = Util.getViewStateId(ctx);
                writer.writeAttribute("id", viewStateId, null);
            }
            writer.writeAttribute("value", id, null);
            if (webConfig.isOptionEnabled(AutoCompleteOffOnViewState)) {
                writer.writeAttribute("autocomplete", "off", null);
            }
            writer.endElement("input");

            writeClientWindowField(ctx, writer);
            writeRenderKitIdField(ctx, writer);
        }
    }

    @Override
    public Object getState(FacesContext ctx, String viewId) {

        String compoundId = getStateParamValue(ctx);

        if (compoundId == null) {
            return null;
        }

        if ("stateless".equals(compoundId)) {
            return "stateless";
        }

        int sep = compoundId.indexOf(':');
        assert (sep != -1);
        assert (sep < compoundId.length());

        String idInLogicalMap = compoundId.substring(0, sep);
        String idInActualMap = compoundId.substring(sep + 1);

        ExternalContext externalCtx = ctx.getExternalContext();
        Object sessionObj = externalCtx.getSession(false);

        // stop evaluating if the session is not available
        if (sessionObj == null) {
            if (log.isTraceEnabled()) {
                log.trace(String.format(
                        "Unable to restore server side state for view ID %s as no session is available", viewId));
            }
            return null;
        }

        // noinspection SynchronizationOnLocalVariableOrMethodParameter
        synchronized (sessionObj) {
            Map<String, Map> conversationMap = (Map) externalCtx.getSessionMap().get(CONVERSATION_VIEW_MAP);
            if (conversationMap == null) {
                return null;
            }
            Map logicalMap = null;
            for (Map lm : conversationMap.values()) {
                if (lm.get(idInLogicalMap) != null) {
                    logicalMap = lm;
                    break;
                }
            }

            if (logicalMap != null) {
                Map actualMap = (Map) logicalMap.get(idInLogicalMap);
                if (actualMap != null) {
                    RequestStateManager.set(ctx, RequestStateManager.LOGICAL_VIEW_MAP, idInLogicalMap);
                    Object[] state = (Object[]) actualMap.get(idInActualMap);
                    Object[] restoredState = new Object[2];

                    restoredState[0] = state[0];
                    restoredState[1] = state[1];

                    if (state != null) {
                        RequestStateManager.set(ctx, RequestStateManager.ACTUAL_VIEW_MAP, idInActualMap);
                        if (state.length == 2 && state[1] != null) {
                            restoredState[1] = handleRestoreState(state[1]);
                        }
                    }

                    return restoredState;
                }
            }
        }

        return null;

    }

    /**
     * @param ctx the <code>FacesContext</code> for the current request
     * @return a unique ID for building the keys used to store views within a session
     */
    private String createIncrementalRequestId(FacesContext ctx) {
        Map<String, Object> sm = ctx.getExternalContext().getSessionMap();
        AtomicInteger idgen = (AtomicInteger) sm.get(STATEMANAGED_SERIAL_ID_KEY);
        if (idgen == null) {
            idgen = new AtomicInteger(1);
        }
        // always call put/setAttribute as we may be in a clustered environment.
        sm.put(STATEMANAGED_SERIAL_ID_KEY, idgen);
        return (UIViewRoot.UNIQUE_ID_PREFIX + idgen.getAndIncrement());

    }

    private String createRandomId() {
        return Long.valueOf(random.nextLong()).toString();
    }

}