Package org.apache.myfaces.orchestra.conversation

Source Code of org.apache.myfaces.orchestra.conversation.ConversationManager

/*
* 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 org.apache.myfaces.orchestra.conversation;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.myfaces.orchestra.frameworkAdapter.FrameworkAdapter;
import org.apache.myfaces.orchestra.requestParameterProvider.RequestParameterProviderManager;

import java.io.IOException;
import java.io.ObjectStreamException;
import java.io.Serializable;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

/**
* Deals with the various conversation contexts in the current session.
* <p>
* A new conversation context will be created if the servlet request did
* not specify an existing conversation context id.
* <p>
* At the current time, this object does not serialize well. Any attempt to serialize
* this object (including any serialization of the user session) will just cause it
* to be discarded.
* <p>
* TODO: fix serialization issues.
*/
public class ConversationManager implements Serializable
{
    private static final long serialVersionUID = 1L;

    final static String CONVERSATION_CONTEXT_PARAM = "conversationContext";

    private final static String CONVERSATION_MANAGER_KEY = "org.apache.myfaces.ConversationManager";
    private final static String CONVERSATION_CONTEXT_REQ = "org.apache.myfaces.ConversationManager.conversationContext";

    private static final Iterator EMPTY_ITERATOR = Collections.EMPTY_LIST.iterator();

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

    /**
     * Used to generate a unique id for each "window" that a user has open
     * on the same webapp within the same HttpSession. Note that this is a
     * property of an object stored in the session, so will correctly
     * migrate from machine to machine along with a distributed HttpSession.
     *
     */
    private long nextConversationContextId = 1;

    // This member must always be accessed with a lock held on the parent ConverstationManager instance;
    // a HashMap is not thread-safe and this class must be thread-safe.
    private final Map conversationContexts = new HashMap();

    protected ConversationManager()
    {
    }

    /**
     * Get the conversation manager. This creates a new one if none exists.
     */
    public static ConversationManager getInstance()
    {
        return getInstance(true);
    }

    /**
     * Get the conversation manager.
     * <p>
     * When create is true, an instance is always returned; one is
     * created if none currently exists for the current user session.
     * <p>
     * When create is false, null is returned if no instance yet
     * exists for the current user session.
     */
    public static ConversationManager getInstance(boolean create)
    {
        FrameworkAdapter frameworkAdapter = FrameworkAdapter.getCurrentInstance();
        if (frameworkAdapter == null)
        {
            if (!create)
            {
                // if we should not created one, it doesn't matter if there is no
                // FrameworkAdapter available.
                return null;
            }
            else
            {
                throw new IllegalStateException("FrameworkAdapter not found");
            }
        }

        ConversationManager conversationManager = (ConversationManager) frameworkAdapter.getSessionAttribute(
                CONVERSATION_MANAGER_KEY);
        if (conversationManager == null && create)
        {
            // TODO: do not call new directly here, as it makes it impossible to configure
            // an alternative ConversationManager instance. This is IOC and test unfriendly.
            conversationManager = new ConversationManager();

            // initialize environmental systems
            RequestParameterProviderManager.getInstance().register(new ConversationRequestParameterProvider());

            // set mark
            FrameworkAdapter.getCurrentInstance().setSessionAttribute(CONVERSATION_MANAGER_KEY, conversationManager);
        }

        return conversationManager;
    }

    /**
     * Get the current conversationContextId.
     * <p>
     * If there is no current conversationContext, then null is returned.
     */
    private Long findConversationContextId()
    {
        FrameworkAdapter fa = FrameworkAdapter.getCurrentInstance();
       
        // Has it been extracted from the req params and cached as a req attr?
        Long conversationContextId = (Long)fa.getRequestAttribute(CONVERSATION_CONTEXT_REQ);
        if (conversationContextId == null)
        {
            if (fa.containsRequestParameterAttribute(CONVERSATION_CONTEXT_PARAM))
            {
                String urlConversationContextId = fa.getRequestParameterAttribute(
                        CONVERSATION_CONTEXT_PARAM).toString();
                conversationContextId = new Long(
                        Long.parseLong(urlConversationContextId, Character.MAX_RADIX));
            }
        }
        return conversationContextId;
    }
   
    /**
     * Get the current, or create a new unique conversationContextId.
     * <p>
     * The current conversationContextId will be retrieved from the request
     * parameters. If no such parameter is present then a new id will be
     * allocated <i>and configured as the current conversation id</i>.
     * <p>
     * In either case the result will be stored within the request for
     * faster lookup.
     * <p>
     * Note that there is no security flaw regarding injection of fake
     * context ids; the id must match one already in the session and there
     * is no security problem with two windows in the same session exchanging
     * ids.
     * <p>
     * This method <i>never</i> returns null.
     */
    private Long getOrCreateConversationContextId()
    {
        Long conversationContextId = findConversationContextId();
        if (conversationContextId == null)
        {
            conversationContextId = createNextConversationContextId();
            FrameworkAdapter fa = FrameworkAdapter.getCurrentInstance();
            fa.setRequestAttribute(CONVERSATION_CONTEXT_REQ, conversationContextId);
        }

        return conversationContextId;
    }

    /**
     * Get the current, or create a new unique conversationContextId.
     * <p>
     * This method is deprecated because, unlike all the other get methods, it
     * actually creates the value if it does not exist. Other get methods (except
     * getInstance) return null if the data does not exist. In addition, this
     * method is not really useful to external code and probably should never
     * have been exposed as a public API in the first place; external code should
     * never need to force the creation of a ConversationContext.
     * <p>
     * For internal use within this class, use either findConversationContextId()
     * or getOrCreateConversationContextId().
     * <p>
     * To just obtain the current ConversationContext <i>if it exists</i>, see
     * method getCurrentConversationContext().
     *
     * @deprecated This method should not be needed by external classes, and
     * was inconsistent with other methods on this class.
     */
    public Long getConversationContextId()
    {
        return getOrCreateConversationContextId();
    }

    /**
     * Allocate a new Long value for use as a conversation context id.
     * <p>
     * The returned value must not match any conversation context id already in
     * use within this ConversationManager instance (which is scoped to the
     * current http session).
     */
    private Long createNextConversationContextId()
    {
        Long conversationContextId;
        synchronized(this)
        {
            conversationContextId = new Long(nextConversationContextId);
            nextConversationContextId++;
        }
        return conversationContextId;
    }

    /**
     * Get the conversation context for the given id.
     * <p>
     * Null is returned if there is no ConversationContext with the specified id.
     * <p>
     * Param conversationContextId must not be null.
     */
    protected ConversationContext getConversationContext(Long conversationContextId)
    {
        synchronized (this)
        {
            return (ConversationContext) conversationContexts.get(conversationContextId);
        }
    }

    /**
     * Get the conversation context for the given id.
     * <p>
     * If there is no such conversation context a new one will be created.
     * The new conversation context will be a "top-level" context (ie has no parent).
     * <p>
     * The new conversation context will <i>not</i> be the current conversation context,
     * unless the id passed in was already configured as the current conversation context id.
     */
    protected ConversationContext getOrCreateConversationContext(Long conversationContextId)
    {
        synchronized (this)
        {
            ConversationContext conversationContext = (ConversationContext) conversationContexts.get(
                    conversationContextId);
            if (conversationContext == null)
            {
                conversationContext = new ConversationContext(null, conversationContextId.longValue());
                conversationContexts.put(conversationContextId, conversationContext);

                // TODO: add the "user" name here, otherwise this debugging is not very useful
                // except when testing a webapp with only one user.
                log.debug("Created context " + conversationContextId);
            }
            return conversationContext;
        }
    }

    /**
     * Make the specific context the current context for the current HTTP session.
     * <p>
     * Methods like getCurrentConversationContext will then return the specified
     * context object.
     *
     * @since 1.2
     */
    public void activateConversationContext(ConversationContext ctx)
    {
        FrameworkAdapter fa = FrameworkAdapter.getCurrentInstance();
        fa.setRequestAttribute(CONVERSATION_CONTEXT_REQ, ctx.getIdAsLong());
    }

    /**
     * Ends all conversations within the current context; the context itself will remain active.
     */
    public void clearCurrentConversationContext()
    {
        Long conversationContextId = findConversationContextId();
        if (conversationContextId != null)
        {
            ConversationContext conversationContext = getConversationContext(conversationContextId);
            if (conversationContext != null)
            {
                conversationContext.clear();
            }
        }
    }

    /**
     * Destroy the given conversation context.
     * <p>
     * Note: it is assumed that the context is already been destroyed
     */
    protected void removeConversationContext(Long conversationContextId)
    {
        synchronized (this)
        {
            conversationContexts.remove(conversationContextId);
        }
    }

    /**
     * Start a conversation.
     *
     * @see ConversationContext#startConversation(String, ConversationFactory)
     */
    public Conversation startConversation(String name, ConversationFactory factory)
    {
        ConversationContext conversationContext = getOrCreateCurrentConversationContext();
        return conversationContext.startConversation(name, factory);
    }

    /**
     * Remove a conversation
     *
     * Note: It is assumed that the conversation has already been invalidated
     *
     * @see ConversationContext#removeConversation(String)
     */
    protected void removeConversation(String name)
    {
        Long conversationContextId = findConversationContextId();
        if (conversationContextId != null)
        {
            ConversationContext conversationContext = getConversationContext(conversationContextId);
            if (conversationContext != null)
            {
                conversationContext.removeConversation(name);
            }
        }
    }

    /**
     * Get the conversation with the given name
     *
     * @return null if no conversation context is active or if the conversation did not exist.
     */
    public Conversation getConversation(String name)
    {
        ConversationContext conversationContext = getCurrentConversationContext();
        if (conversationContext == null)
        {
            return null;
        }
        return conversationContext.getConversation(name);
    }

    /**
     * check if the given conversation is active
     */
    public boolean hasConversation(String name)
    {
        ConversationContext conversationContext = getCurrentConversationContext();
        if (conversationContext == null)
        {
            return false;
        }
        return conversationContext.hasConversation(name);
    }

    /**
     * Returns an iterator over all the Conversation objects in the current conversation
     * context. Never returns null, even if no conversation context exists.
     */
    public Iterator iterateConversations()
    {
        ConversationContext conversationContext = getCurrentConversationContext();
        if (conversationContext == null)
        {
            return EMPTY_ITERATOR;
        }

        return conversationContext.iterateConversations();
    }

    /**
     * Get the current conversation context.
     * <p>
     * In a simple Orchestra application this will always be a root conversation context.
     * When using a dialog/page-flow environment the context that is returned might have
     * a parent context.
     * <p>
     * Null is returned if there is no current conversationContext.
     */
    public ConversationContext getCurrentConversationContext()
    {
        Long ccid = findConversationContextId();
        if (ccid == null)
        {
            return null;
        }
        else
        {
            return getConversationContext(ccid);
        }
    }

    /**
     * Return the current ConversationContext for the current http session;
     * if none yet exists then a ConversationContext is created and configured
     * as the current context.
     * <p>
     * This is currently package-scoped because it is not clear that code
     * outside orchestra can have any use for this method. The only user
     * outside of this class is ConversationRequestParameterProvider.
     *
     * @since 1.2
     */
    ConversationContext getOrCreateCurrentConversationContext()
    {
        Long ccid = getOrCreateConversationContextId();
        return getOrCreateConversationContext(ccid);
    }

    /**
     * Return true if there is a conversation context associated with the
     * current request.
     */
    public boolean hasConversationContext()
    {
        return getCurrentConversationContext() == null;
    }

    /**
     * Get the current root conversation context (aka the window conversation context).
     * <p>
     * Null is returned if it does not exist.
     *
     * @since 1.2
     */
    public ConversationContext getCurrentRootConversationContext()
    {
        Long ccid = findConversationContextId();
        if (ccid == null)
        {
            return null;
        }

        synchronized (this)
        {
            ConversationContext conversationContext = getConversationContext(ccid);
            if (conversationContext == null)
            {
                return null;
            }
            else
            {
                return conversationContext.getRoot();
            }
        }
    }

    /**
     * Get the Messager used to inform the user about anomalies.
     * <p>
     * What instance is returned is controlled by the FrameworkAdapter. See
     * {@link org.apache.myfaces.orchestra.frameworkAdapter.FrameworkAdapter} for details.
     */
    public ConversationMessager getMessager()
    {
        return FrameworkAdapter.getCurrentInstance().getConversationMessager();
    }

    /**
     * Check the timeout for each conversation context, and all conversations
     * within those contexts.
     * <p>
     * If any conversation has not been accessed within its timeout period
     * then clear the context.
     * <p>
     * Invoke the checkTimeout method on each context so that any conversation
     * that has not been accessed within its timeout is invalidated.
     */
    protected void checkTimeouts()
    {
        Map.Entry[] contexts;
        synchronized (this)
        {
            contexts = new Map.Entry[conversationContexts.size()];
            conversationContexts.entrySet().toArray(contexts);
        }

        long checkTime = System.currentTimeMillis();

        for (int i = 0; i<contexts.length; i++)
        {
            Map.Entry context = contexts[i];

            Long conversationContextId = (Long) context.getKey();
            ConversationContext conversationContext = (ConversationContext) context.getValue();
            conversationContext.checkConversationTimeout();

            if (conversationContext.getTimeout() > -1 &&
                (conversationContext.getLastAccess() +
                conversationContext.getTimeout()) < checkTime)
            {
                if (log.isDebugEnabled())
                {
                    log.debug("end conversation context due to timeout: " + conversationContext.getId());
                }

                conversationContext.clear();
                synchronized (this)
                {
                    conversationContexts.remove(conversationContextId);
                }
            }
        }
    }

    private void writeObject(java.io.ObjectOutputStream out) throws IOException
    {
        // the conversation manager is not (yet) serializable, we just implement it
        // to make it work with distributed sessions
    }

    private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException
    {
        // nothing written, so nothing to read
    }

    private Object readResolve() throws ObjectStreamException
    {
        // do not return a real object, that way on first request a new conversation manager will be created
        return null;
    }
}
TOP

Related Classes of org.apache.myfaces.orchestra.conversation.ConversationManager

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.